View Javadoc

1   /***
2    * 
3    */
4   package org.telscenter.pas.steps;
5   
6   import java.awt.Component;
7   import java.beans.beancontext.BeanContextServices;
8   import java.io.File;
9   import java.io.FileWriter;
10  import java.io.UnsupportedEncodingException;
11  import java.lang.reflect.Method;
12  import java.net.MalformedURLException;
13  import java.net.URL;
14  import java.net.URLEncoder;
15  import java.util.HashMap;
16  import java.util.Locale;
17  import java.util.Map;
18  import java.util.Set;
19  import java.util.TooManyListenersException;
20  import java.util.Map.Entry;
21  import java.util.logging.Level;
22  import java.util.logging.Logger;
23  
24  import javax.swing.JOptionPane;
25  
26  import net.sf.sail.core.beans.service.SessionService;
27  
28  import org.telscenter.pas.ui.browser.BrowserEvent;
29  import org.telscenter.pas.ui.browser.IBrowser;
30  import org.telscenter.pas.ui.browser.IBrowserListener;
31  
32  /***
33   * Pas step type that adapts a legacy WISE-2 module, conformant to the <a
34   * href="http://www.telscenter.org/confluence/display/WPS/Module+API">WISE v2
35   * Module API</a> specification.
36   * 
37   * This is the API used by the <i>WISE Original Browser-based Learning
38   * Environment</i> aka the <b>WOBBLE</b>.
39   * 
40   * @author turadg
41   * 
42   */
43  
44  // FIXME inheriting from BrowseWeb right now but should be refactored since
45  // WobbleModule doesn't have a simple URL property
46  public class WobbleModule extends BrowseWeb {
47  
48  	private static final long serialVersionUID = 1L;
49  
50  	/***
51  	 * Logger for this class
52  	 */
53  	private static final Logger logger = Logger.getLogger(WobbleModule.class
54  			.getName());
55  
56  	/***
57  	 * We have to use a static variable for this because sail doesn't provide a
58  	 * session service for our data as far as I know.
59  	 */
60  	protected static boolean isWiseSessionSetup = false;
61  
62  	SessionService sessionService;
63  
64  	String moduleOtherData = "";
65  
66  	int moduleOtherNumber = 0;
67  
68  	String wiseStepUrl;
69  
70  	/***
71  	 * 
72  	 */
73  	public WobbleModule() {
74  		setMockSettings();
75  	}
76  
77  	// TODO should we make this part of abstract PasStep?
78  	// TODO should we rename PasStep to AbstractPasStep?
79  	public void setMockSettings() {
80  		// set up parameters to match step 190176
81  		/*
82  		 * <title>Self Test 1</title>
83  		 * <url>/modules/selftest/studentSelfTest.php</url> <assessmentURL />
84  		 * <authoringURL>/modules/selftest/authorSelfTest.php</authoringURL>
85  		 * <otherData /> <otherNumber>451</otherNumber> <dataToSend>full</dataToSend>
86  		 * <iconURL>/student/images/icon1/test2.gif</iconURL> <frameName>main</frameName>
87  		 * <moduleDataID>216673</moduleDataID> <iteration>1</iteration>
88  		 * <duplicationURL>/modules/selftest/duplicateSelfTest.php</duplicationURL>
89  		 * <assessmentExtURL /> <isHingeSelectionStep /> <isHintsStep />
90  		 * <isEvSelectionStep /> <isAllWorkStep /> <isSubsidiary>0</isSubsidiary>
91  		 * <bigAuthoringWindow>0</bigAuthoringWindow> <isComplete>1</isComplete>
92  		 * <hingeID>0</hingeID> <isReflected>0</isReflected> <knownModuleID>24</knownModuleID>
93  		 * 
94  		 */
95  		try {
96  			// for "Self Test 1" step in 11752
97  			// setTitle("Self Test 1");
98  			// setModuleOtherNumber(451);
99  			// String studentUrlStr =
100 			// "http://wise.berkeley.edu/modules/selftest/studentSelfTest.php";
101 
102 			// for "Discussion 1" step in 11752
103 			setTitle("Discussion 1");
104 			setModuleOtherNumber(1006);
105 			String studentUrlStr = "http://wise.berkeley.edu/modules/forum/studentDiscussion.php";
106 
107 			URL studentUrl = new URL(studentUrlStr);
108 			setUrl(studentUrl);
109 		} catch (MalformedURLException e) {
110 			// TODO Auto-generated catch block
111 			e.printStackTrace();
112 		}
113 	}
114 
115 	/*
116 	 * (non-Javadoc)
117 	 * 
118 	 * @see org.telscenter.pas.steps.BrowseWeb#getComponent()
119 	 */
120 	public Component getComponent() {
121 		Component component = super.getComponent();
122 		if (!isWiseSessionSetup) {
123 			// look for the wise.sessionId in the sail session object
124 			// pass that id to the wise server so the login cookie is
125 			// setup correct in the browser
126 			String sessionId = sessionService.getProperty("wise.sessionId",
127 					null);
128 			String wiseServer = sessionService.getProperty("wise.wiseServer",
129 					null);
130 			if (sessionId != null && wiseServer != null) {
131 				String urlStr = "http://" + wiseServer
132 						+ "/SAIL/setSessionCookie.php?WISESESSIONID="
133 						+ sessionId;
134 				logger.info("setupcookie url: " + urlStr);
135 
136 				try {
137 					browser.addBrowserListener(new IBrowserListener() {
138 						public void downloadCompleted(BrowserEvent event) {
139 							// this can be dangerous, se we'll need to make sure
140 							// our
141 							// browser implementations handle this correctly
142 							browser.removeBrowserListener(this);
143 
144 							// seems like this short circuits the setting of the
145 							// cookie in the browser. Sleeping for a bit here
146 							// until the browser updates seems like it works
147 							// but this is pretty hacky
148 							// 500 ms worked for some, but 1000 seems more safe
149 							(new Thread() {
150 								public void run() {
151 									try {
152 										Thread.sleep(1000);
153 									} catch (InterruptedException e) {
154 										// TODO Auto-generated catch block
155 										e.printStackTrace();
156 									}
157 									isWiseSessionSetup = true;
158 									setBrowserUrl();
159 								}
160 							}).start();
161 						}
162 
163 						public void documentCompleted(BrowserEvent event) {
164 						}
165 
166 						public void downloadProgress(BrowserEvent event) {
167 						}
168 					});
169 
170 					// The browser might never report that the download is
171 					// complete
172 					// in this case we need to simply set the browser url. and
173 					// if it
174 					// wasn't successful they will have to log in.
175 					(new Thread() {
176 						public void run() {
177 							try {
178 								Thread.sleep(1500);
179 								if (!isWiseSessionSetup) {
180 									isWiseSessionSetup = true;
181 									setBrowserUrl();
182 								}
183 							} catch (InterruptedException e) {
184 								e.printStackTrace();
185 							}
186 						}
187 					}).start();
188 
189 					browser.setUrl(new URL(urlStr));
190 
191 					// We are all done for now the real url will be set
192 					// after this one has loaded.
193 					return component;
194 				} catch (MalformedURLException e) {
195 					logger.log(Level.INFO, "can't setup cookie in wise", e);
196 				}
197 			} else {
198 				logger.info("no wise sessionId, user will have to login in");
199 			}
200 		}
201 
202 		setBrowserUrl();
203 		return component;
204 	}
205 
206 	protected void setBrowserUrl() {
207 		// POST before returning
208 		Map<String, String> studentModeParameters = getStudentModeParameters(
209 				moduleOtherData, moduleOtherNumber);
210 		String postData = encodeParameterMap(studentModeParameters);
211 
212 		String stepUrlStr = getWiseStepUrl();
213 
214 		// Check to see if this is a curnit that was created with an old
215 		// converter. In that case getWiseStepUrl will return null.
216 		// and instead getUrl will contain the url.
217 		if (stepUrlStr == null) {
218 			URL origUrl = getUrl();
219 			String path = origUrl.getPath();
220 			String query = origUrl.getQuery();
221 			stepUrlStr = "";
222 			if (path != null) {
223 				stepUrlStr += path;
224 			}
225 			if (query != null) {
226 				stepUrlStr += query;
227 			}
228 		}
229 
230 		String wiseServer = sessionService.getProperty("wise.wiseServer",
231 				"wise.berkeley.edu");
232 		URL browserUrl;
233 		try {
234 			URL wiseUrl = new URL("http://" + wiseServer);
235 			browserUrl = new URL(wiseUrl, stepUrlStr);
236 
237 			// need to verify the jdic implementation on the mac returns safari
238 			String browserType = browser.getBrowserType();
239 			logger.info("browserType: " + browserType);
240 			if (browserType == IBrowser.SAFARI) {
241 				browserUrl = new URL(browserUrl.toExternalForm() + "?"
242 						+ postData);
243 				logger.info("legacy wise step url: " + browserUrl);
244 				try {
245 					openExternalBrowser(browserUrl);
246 					// need to put some text in the browser lets put the wise
247 					// site
248 					File tmpFile = File.createTempFile("loading", ".html");
249 					tmpFile.deleteOnExit();
250 					FileWriter writer = new FileWriter(tmpFile);
251 					writer
252 							.append("A browser window should be opening with this step.");
253 					writer.close();
254 					browser.setUrl(tmpFile.toURL());
255 				} catch (Exception exp) {
256 					logger.log(Level.WARNING, "Can't open external browser.",
257 							exp);
258 					JOptionPane
259 							.showMessageDialog(
260 									null,
261 									"Mac OSX currently requires and external "
262 											+ "browser to display legacy WISE steps.  An external brower could not "
263 											+ "be successfully opened.  Please check the logs for more info.  The "
264 											+ "embedded jdic will be used but it is not stable.",
265 									"No External Browser",
266 									JOptionPane.WARNING_MESSAGE);
267 					// it seems the postData is ignored on OSX right now
268 					// which is why the external browser is needed.				
269 					browser.setUrl(url, postData);
270 				}
271 
272 			} else {
273 				logger.info("legacy wise step url (not including post data): "
274 						+ browserUrl);
275 				browser.setUrl(browserUrl, postData);
276 			}
277 		} catch (MalformedURLException e) {
278 			// FIXME should set the url to something for an error message
279 			e.printStackTrace();
280 		}
281 	}
282 
283 	public void openExternalBrowser(URL url) throws Exception {
284 		Class<?> serviceManager = Class.forName("javax.jnlp.ServiceManager");
285 		Method lookupMethod = serviceManager.getMethod("lookup",
286 				new Class[] { String.class });
287 		Object basicService = lookupMethod.invoke(null,
288 				new Object[] { "javax.jnlp.BasicService" });
289 		Method showDocument = basicService.getClass().getMethod("showDocument",
290 				new Class[] { URL.class });
291 		showDocument.invoke(basicService, new Object[] { url });
292 		return;
293 	}
294 
295 	/***
296 	 * @param studentModeParameters
297 	 * @return
298 	 */
299 	private String encodeParameterMap(Map<String, String> studentModeParameters) {
300 		StringBuffer sb = new StringBuffer();
301 		Set<Entry<String, String>> entrySet = studentModeParameters.entrySet();
302 		for (Entry<String, String> pair : entrySet) {
303 			String name = pair.getKey();
304 			String value = pair.getValue();
305 			try {
306 				sb.append(URLEncoder.encode(name, "UTF-8"));
307 				sb.append("=");
308 				sb.append(URLEncoder.encode(value, "UTF-8"));
309 				sb.append("&");
310 			} catch (UnsupportedEncodingException e) {
311 				// TODO Auto-generated catch block
312 				e.printStackTrace();
313 			}
314 		}
315 		return sb.toString();
316 	}
317 
318 	protected Map<String, String> getStudentModeParameters(String otherData,
319 			int otherNumber) {
320 		// comments from
321 		// http://www.telscenter.org/confluence/display/WPS/Module+API
322 		Map<String, String> p = new HashMap<String, String>();
323 
324 		// inWISE - always 1
325 		p.put("inWISE", "1");
326 
327 		// mode - 1 for regular project mode, 0 for demo mode NOT YET
328 		// IMPLEMENTED,
329 		p.put("mode", "1");
330 
331 		// projectName - human name of the running project
332 		addUrlParamFromSession(p, "projectName", "a project for testing");
333 
334 		// projectID - ID number of the running project
335 		// FIXME this should be removed from the API as it's implied by groupID
336 		addUrlParamFromSession(p, "projectID", "130670");
337 
338 		// groupID - ID number of the group of students calling the module
339 		// default is Turadg in project 11752
340 		addUrlParamFromSession(p, "groupID", "130670");
341 
342 		// wiseServer - the name of the WISE server that holds the currently
343 		// running project NOTE: groupID does NOT constitute a unique index
344 		// unless it is combined with wiseServer. For example, there could be
345 		// group with ID number 702 on wise.berkeley.edu and a different group
346 		// with ID 702 on wise.ils.uio.no. This is true also of projectID.
347 		addUrlParamFromSession(p, "wiseServer", "wise.berkeley.edu");
348 
349 		// language - the two-letter language code of the currently running
350 		// project
351 		// TODO should this come from wise or from the java environment
352 		addUrlParamFromSession(p, "language", Locale.getDefault().getLanguage());
353 
354 		// groupUsernames - a comma-separated list of the usernames of the
355 		// students calling the module NOTE: usernames are also not
356 		// necessarily unique unless combined with wiseServer
357 		addUrlParamFromSession(p, "groupUsernames", "turadg");
358 
359 		// groupRealnames - a comma-separated list of the human names of the
360 		// students calling the module
361 		addUrlParamFromSession(p, "groupRealnames", "Turadg Aleahmad");
362 
363 		// isCurrent - 1 if the group has a current, up-to-date registration
364 		// with WISE, 0 if their registration is not up-to-date (ie, from a
365 		// previous term or year)
366 		addUrlParamFromSession(p, "isCurrent", "1"); // I don't know anything
367 														// that observes this
368 
369 		// teacher - human name of the current group's teacher
370 		addUrlParamFromSession(p, "teacher", "FIXME teacher");
371 
372 		// period - period number of the current group
373 		addUrlParamFromSession(p, "period", "1");
374 
375 		// otherData - module-defined field (see Authoring)
376 		p.put("otherData", otherData);
377 
378 		// otherNumber - module-defined field (see Authoring)
379 		p.put("otherNumber", otherNumber + "");
380 
381 		// hingeSelection - if the current project contains a hinge, the value
382 		// of the current group's selection is passed here
383 		addUrlParamFromSession(p, "hingeSelection", "FIXME hingeSelection");
384 
385 		// iteration - if the current step is a reflection step (set by "revisit
386 		// a previous step" in the PM), then this field will contain the
387 		// iteration number (see example under Other Notes)
388 		addUrlParamFromSession(p, "iteration", "FIXME iteration");
389 
390 		// style - 1 for "Classic" interface style, 2 for "Expert"
391 		p.put("style", "1"); // TODO stick with Classic for now
392 
393 		return p;
394 	}
395 
396 	/***
397 	 * This method uses the sessionService to lookup the key and if it isn't
398 	 * there then it uses the default value.
399 	 * 
400 	 * @param p
401 	 * @param key
402 	 * @param defaultValue
403 	 */
404 	protected void addUrlParamFromSession(Map<String, String> p, String key,
405 			String defaultValue) {
406 		p.put(key, sessionService.getProperty("wise." + key, defaultValue));
407 	}
408 
409 	/***
410 	 * @return the apiOtherData
411 	 */
412 	public String getModuleOtherData() {
413 		return moduleOtherData;
414 	}
415 
416 	/***
417 	 * @param apiOtherData
418 	 *            the apiOtherData to set
419 	 */
420 	public void setModuleOtherData(String apiOtherData) {
421 		this.moduleOtherData = apiOtherData;
422 	}
423 
424 	/***
425 	 * @return the apiOtherNumber
426 	 */
427 	public int getModuleOtherNumber() {
428 		return moduleOtherNumber;
429 	}
430 
431 	/***
432 	 * @param apiOtherNumber
433 	 *            the apiOtherNumber to set
434 	 */
435 	public void setModuleOtherNumber(int apiOtherNumber) {
436 		this.moduleOtherNumber = apiOtherNumber;
437 	}
438 
439 	protected void consumeService(BeanContextServices bcs, Class<?> serviceClass) {
440 		super.consumeService(bcs, serviceClass);
441 		if (serviceClass == SessionService.class) {
442 			try {
443 				sessionService = (SessionService) bcs.getService(this, this,
444 						SessionService.class, this, this);
445 			} catch (TooManyListenersException e1) {
446 				logger
447 						.severe("BeanContextServices, Class -  : exception: " + e1); //$NON-NLS-1$ 
448 			}
449 		}
450 	}
451 
452 	public String getWiseStepUrl() {
453 		return wiseStepUrl;
454 	}
455 
456 	public void setWiseStepUrl(String wiseStepUrl) {
457 		this.wiseStepUrl = wiseStepUrl;
458 	}
459 }