View Javadoc

1   /*
2    * Created on Apr 25, 2005, Copyright UC Regents
3    */
4   package org.telscenter.sailotrunk;
5   
6   import java.awt.Dimension;
7   import java.awt.event.ActionEvent;
8   import java.awt.event.ActionListener;
9   import java.awt.event.WindowAdapter;
10  import java.awt.event.WindowEvent;
11  import java.beans.beancontext.BeanContextServices;
12  import java.io.ByteArrayInputStream;
13  import java.io.ByteArrayOutputStream;
14  import java.io.File;
15  import java.io.StringReader;
16  import java.net.URL;
17  import java.util.TimerTask;
18  import java.util.TooManyListenersException;
19  import java.util.logging.Logger;
20  
21  import javax.swing.AbstractAction;
22  import javax.swing.Action;
23  import javax.swing.JFileChooser;
24  import javax.swing.JFrame;
25  import javax.swing.JOptionPane;
26  import javax.swing.JScrollPane;
27  import javax.swing.JTextArea;
28  import java.util.Timer;
29  
30  import net.sf.sail.common.beansupport.ITitleAware;
31  import net.sf.sail.common.beansupport.SailBeanContextChildSupport;
32  import net.sf.sail.core.beans.event.SessionEvent;
33  import net.sf.sail.core.beans.event.SessionEventListener;
34  import net.sf.sail.core.beans.service.AgentService;
35  import net.sf.sail.core.beans.service.SessionService;
36  import net.sf.sail.core.entity.IAgent;
37  import net.sf.sail.core.entity.ISock;
38  import net.sf.sail.core.entity.MismatchedAgentSetSizeException;
39  import net.sf.sail.core.entity.Rim;
40  import net.sf.sail.core.entity.Role;
41  import net.sf.sail.core.entity.UnsupportedRimShapeException;
42  
43  import org.concord.framework.otrunk.OTObject;
44  import org.concord.framework.otrunk.view.OTViewEntry;
45  import org.concord.framework.otrunk.view.OTViewFactory;
46  import org.concord.otrunk.OTMLToXHTMLConverter;
47  import org.concord.otrunk.datamodel.OTDatabase;
48  import org.concord.otrunk.view.OTConfig;
49  import org.concord.otrunk.view.OTViewChild;
50  import org.concord.otrunk.view.OTViewer;
51  import org.concord.otrunk.view.OTViewerHelper;
52  import org.concord.otrunk.xml.XMLDatabase;
53  
54  /***
55   * Bean which should be the root bean of the curnit.   
56   * It starts up the OTViewer and saves its data into a rim.
57   * 
58   * @author scytacki
59   */
60  public class SailOTViewer extends SailBeanContextChildSupport 
61  	implements ITitleAware, OTPodAdapter
62  {
63  	private static final String EXPORT_HTML_DIALOG_TAG = "_dialog_";
64  
65  	public static final String SAILOTRUNK_VIEW_LEARNEROTML = "sailotrunk.learnerotml.edit";
66  
67  	public static final String SAILOTRUNK_OTMLURL = "sailotrunk.otmlurl";
68  
69  	public static final String SAILOTRUNK_EXPORT_HTML = "sailotrunk.export.html";
70  	
71  	static {
72  		// I don't know if this will be soon enough
73  		// but lets see
74          System.setProperty("apple.laf.useScreenMenuBar","true");
75  	}
76  	
77  	/***
78  	 * Logger for this class
79  	 */
80  	private static final Logger logger = Logger.getLogger(SailOTViewer.class
81  			.getName());
82  
83  	private static final long serialVersionUID = 1L;
84  	
85  	private URL authoredDataURL;
86  
87  	private AgentService agentService;
88  	private SessionService sessionService;
89  		
90  	private Rim workRim;
91  
92  	private ISock workSock;
93  
94  	private boolean hideTree = false;
95  	private int userMode = 0; 
96  		
97  	OTViewer viewer = null;
98  	OTViewerHelper viewerHelper = new OTViewerHelper();
99  	
100 	private JTextArea xmlTextArea;
101 	
102 	SessionEventListener sessionListener = new SessionEventListener() {
103 
104 		private Timer timer;
105 
106 		public void sessionStarted (SessionEvent e) {
107 		}
108 
109 		public void sessionInitiated (SessionEvent e) 
110 		{
111 			String boolStr = sessionService.getProperty(SAILOTRUNK_VIEW_LEARNEROTML, "false");
112 			boolean showLearnerOtmlAsText = Boolean.parseBoolean(boolStr);
113 			if (showLearnerOtmlAsText){
114 				loadDataAsText();
115 				return;
116 			}
117 			
118 			String exportHtmlFolderStr = sessionService.getProperty(SAILOTRUNK_EXPORT_HTML, null);
119 			if (exportHtmlFolderStr != null){
120 				exportHtml(exportHtmlFolderStr);
121 				return;
122 			}
123 			
124 			
125 			viewer = new OTViewer();
126 
127 			viewer.addService(OTPodAdapter.class, SailOTViewer.this);
128 			
129 			viewer.setUserMode(getUserMode());
130 			
131 			String authoredDataUrlStr = getAuthoredDataUrlStr();			
132 			
133 			viewer.init(authoredDataUrlStr);	
134 
135 			AbstractAction exitAction = new AbstractAction(){
136 				/***
137 				 * Not intended to be serialized, just added remove compile warning
138 				 */
139 				private static final long serialVersionUID = 1L;
140 				
141 				public void actionPerformed(ActionEvent e) {
142 					if (timer != null) {
143 						timer.cancel();
144 					}
145                     viewer.getViewContainerPanel().setCurrentObject(null);
146 					saveDataInternal();
147 					
148 					getSessionService().userRequestsTermination();
149 				}				
150 			};
151 			exitAction.putValue(Action.NAME, "Exit");		
152 			
153 			viewer.setExitAction(exitAction);
154 			
155 			viewer.updateMenuBar();
156 			
157 			if(viewer.getUserMode() == OTViewerHelper.NO_USER_MODE){
158 				// if we are in no user mode then we don't need to read data in from the 
159 				// sock or initialize the user data.
160 				return;
161 			}
162 			
163 			timer = new Timer();
164 			long delay = 60*1000; // run every 1 minute
165 			timer.scheduleAtFixedRate(new TimerTask() {
166 				@Override
167 				public void run() {
168 					// System.err.println("Saving otrunk data...");
169 					saveDataInternal();
170 				}
171 			}, delay, delay);
172 			
173 			XMLDatabase sailLearnerDB = getLearnerDatabase();
174 			try {
175 				if(sailLearnerDB != null){
176 					viewer.loadUserDataDb(sailLearnerDB, null);
177 					((XMLDatabase)sailLearnerDB).setDirty(false);				
178 				} else {
179 					viewer.newAnonUserData();
180 				}
181 			} catch (Exception e1) {
182 				// TODO Auto-generated catch block
183 				e1.printStackTrace();
184 			}			
185 		}
186 
187 		public void sessionStopped(SessionEvent e) {
188 			saveDataInternal();
189 		}
190 
191 	};
192 
193 	private boolean needsSockEntryInit = true;
194 	
195 	/***
196 	 * This allows us to view the learner data sent from the SDS as a plain-text file,
197 	 * which we can then edit by hand. Closing the file will then attempt to save the
198 	 * modified OTML learner data back to the SDS as a new bundle.
199 	 * 
200 	 * Warning: No attempt is made to verify the XML, so no guarantees are given if
201 	 * uploading of bad XML is attempted.
202 	 * 
203 	 * @author sfentress
204 	 */
205 	private void loadDataAsText(){
206 		final ISock sock = getWorkSock();
207 		
208 		Object lastEntry = null;
209 		if(sock != null && sock.size() > 0){
210 			lastEntry = sock.peek();
211 		}
212 		
213 		if (lastEntry == null){
214 			System.err.println("Error loading data");
215 			return;
216 		}
217 		
218 		String dataString = null;
219 		if (lastEntry instanceof String){
220 			dataString = (String)lastEntry;
221 		} else if(lastEntry instanceof byte[]){
222 			dataString = new String((byte[])lastEntry);
223 		}
224 		
225 		xmlTextArea = new JTextArea();
226 		xmlTextArea.setLineWrap(true);
227 		xmlTextArea.setWrapStyleWord(true);
228 		
229 		xmlTextArea.setText(dataString);
230 		
231 		final JFrame frame = new JFrame();
232 		
233 		frame.addWindowListener(new WindowAdapter() {
234 			public void windowClosing(WindowEvent e)
235 			{
236 				int saveData = JOptionPane.showConfirmDialog(
237 						frame, "Upload modified bundle?", "Confirm", JOptionPane.YES_NO_OPTION);
238 				
239 				if (saveData == JOptionPane.YES_OPTION){
240 					getSessionService().userRequestsTermination();
241 				}
242 			}
243 		});
244 		
245 		JScrollPane scrollPane = new JScrollPane();
246 		scrollPane.setViewportView(xmlTextArea);
247 		xmlTextArea.setCaretPosition(0);
248 		frame.add(scrollPane);
249 		frame.setPreferredSize(new Dimension(400,400));
250 		frame.pack();
251 		frame.setVisible(true);
252 	}
253 		
254 	protected void exportHtml(String exportHtmlFolder) {		
255 		try{
256 			File fileToSave = null;
257 			
258 			if(EXPORT_HTML_DIALOG_TAG.equals(exportHtmlFolder)){
259 				JFileChooser fileChooser = new JFileChooser();
260 				int result = fileChooser.showSaveDialog(null);
261 				switch(result){
262 				case JFileChooser.APPROVE_OPTION:
263 					fileToSave = fileChooser.getSelectedFile();
264 				case JFileChooser.CANCEL_OPTION:
265 				case JFileChooser.ERROR_OPTION:
266 					break;
267 				}
268 			}
269 			
270 			viewerHelper.addService(OTPodAdapter.class, SailOTViewer.this);
271 			
272 			viewerHelper.setUserMode(getUserMode());
273 					
274 			String authoredDataUrlStr = getAuthoredDataUrlStr();
275 
276 			URL authoredContentURL = new URL(authoredDataUrlStr);
277 			viewerHelper.loadAuthoredContentURL(authoredContentURL);
278 			
279 			if(viewerHelper.getUserMode() == OTViewerHelper.NO_USER_MODE){
280 				// if we are in no user mode then we don't need to read data in from the 
281 				// sock or initialize the user data, just save the html...
282 			} else {
283 				XMLDatabase learnerDatabase = getLearnerDatabase();
284 				if(learnerDatabase != null){
285 					viewerHelper.loadUserData(learnerDatabase, null);
286 				}
287 			}
288 
289 	    	OTObject rootObject = viewerHelper.getRootObject();
290 	    	
291 	    	OTViewEntry rootViewEntry = null;
292 			if(rootObject instanceof OTViewChild){
293 	    		rootViewEntry = ((OTViewChild)rootObject).getViewid();
294 	    		rootObject = ((OTViewChild)rootObject).getObject();
295 	    	}
296 	    	OTViewFactory viewFactory = viewerHelper.getViewFactory();
297 
298 			final OTMLToXHTMLConverter conv = new OTMLToXHTMLConverter(viewFactory, rootObject, 
299 				rootViewEntry,
300 				OTConfig.getSystemPropertyViewMode());
301 
302 			if(fileToSave == null) {
303 				String outputFolderStr = exportHtmlFolder;
304 				if(outputFolderStr == null){
305 					outputFolderStr = System.getProperty("user.home");
306 				}
307 				File outputFolder = new File(outputFolderStr);
308 				outputFolder.mkdirs();
309 				fileToSave = new File(outputFolder, "index.html");
310 			}
311 			
312 			conv.setXHTMLParams(fileToSave, 800, 600);
313 
314 			Thread runner = new Thread(){
315 				public void run() {
316 					conv.run();
317 					System.exit(0);
318 				}
319 				
320 			};
321 			
322 			runner.start();
323 		}
324 		catch (Exception ex) {
325 			ex.printStackTrace();
326 		}
327 	}
328 
329 
330 	protected void registerDesiredServices(BeanContextServices bcs) {
331 	}
332 		
333 	
334 	/***
335 	 * This method can be called more than once in the authoring runtime, when
336 	 * the preview is shown more than once.
337 	 * 
338 	 * @param bcs
339 	 * @param serviceClass
340 	 * @param project
341 	 */
342 	@Override
343 	protected void consumeService(BeanContextServices bcs, Class serviceClass) {
344 		if (serviceClass == SessionService.class) {
345 			try {
346 				sessionService = (SessionService) bcs.getService(this, this,
347 						SessionService.class, this, this);
348 
349 				sessionService.addSessionEventListener(sessionListener);
350 			} catch (TooManyListenersException e1) {
351 				// TODO Auto-generated catch block
352 				logger
353 						.severe("BeanContextServices, Class -  : exception: " + e1); //$NON-NLS-1$
354 			}
355 		}
356 		if (serviceClass == AgentService.class)
357 		{
358 			try
359 			{
360 				final AgentService p = (AgentService) bcs.getService(this,
361 						this, AgentService.class, this, this);
362 				agentService = p;
363 			}
364 			catch (TooManyListenersException e)
365 			{
366 				e.printStackTrace();
367 			}
368 		}
369 	}
370 	
371 	/***
372 	 * @return the sessionService
373 	 */
374 	public SessionService getSessionService() {
375 		return sessionService;
376 	}
377 
378 	/***
379 	 * @param sessionService the sessionService to set
380 	 */
381 	public void setSessionService(SessionService sessionService) {
382 		this.sessionService = sessionService;
383 	}
384 
385 	protected ISock getRimSock(Rim rim)
386 	{
387 		ISock sock = null;
388 		if (rim != null)
389 		{
390 			try {
391 				IAgent agent = agentService.getAgentsInRole(Role.RUN_WORKGROUP).getSingle();
392 				sock = agentService.getSock(rim, agent);
393 			} catch (MismatchedAgentSetSizeException e) {
394 				// TODO Auto-generated catch block
395 				e.printStackTrace();
396 			} catch (UnsupportedRimShapeException e) {
397 				// TODO Auto-generated catch block
398 				e.printStackTrace();
399 			}
400 		}
401 		return sock;
402 	}
403 
404 	public Rim getWorkRim() {
405 		return workRim;
406 	}
407 
408 	public void setWorkRim(Rim workRim) {
409 		this.workRim = workRim;
410 	}
411 
412 	public ISock getWorkSock() {
413 		if (workSock == null) {
414 			workSock = getRimSock(getWorkRim());
415 		}
416 		return workSock;
417 	}
418 
419 
420 	public URL getAuthoredDataURL()
421 	{
422 		return authoredDataURL;
423 	}
424 
425 	public void setAuthoredDataURL(URL url)
426 	{
427 		authoredDataURL = url;
428 	}
429 	
430 	public String getTitle() {
431 		return "SailOTViewer title";
432 	}
433 
434 
435 	public void setTitle(String title) {
436 	}
437 
438 
439 	public boolean isHideTree() {
440 		return hideTree;
441 	}
442 
443 
444 	public void setHideTree(boolean hideTree) {
445 		this.hideTree = hideTree;
446 	}
447 
448 
449 	public int getUserMode() {
450 		return userMode;
451 	}
452 
453 
454 	public void setUserMode(int userMode) {
455 		this.userMode = userMode;
456 	}	
457 	
458 	private void saveDataInternal() {
459 		saveDataInternal(true);
460 	}
461 
462 	private void saveDataInternal(boolean replace)
463 	{
464 		if (needsSockEntryInit ) {
465 			// override replace so that a sockentry gets created in the current sessionbundle
466 			replace = false;
467 		}
468 		
469 		ISock sock = getWorkSock();
470 		
471 		// If we're viewing the straight otml, we don't need to
472 		// use the userDB at all -- we can just upload the text.
473 		String boolStr = sessionService.getProperty(SAILOTRUNK_VIEW_LEARNEROTML, "false");
474 		boolean showLearnerOtmlAsText = Boolean.parseBoolean(boolStr);
475 		if (showLearnerOtmlAsText){
476 			if(getWorkRim().getShape() == byte[].class){
477 				sock.add(xmlTextArea.getText().getBytes(), replace);
478 			} else {
479 				sock.add(xmlTextArea.getText(), replace);
480 			}
481 			return;
482 		}
483 		
484 		OTDatabase userDB = viewer.getUserDataDb();
485 
486 		if(userDB == null){
487 			// no user database was created 
488 			return;
489 		}
490 		
491 		// If the database hasn't been modified, don't
492 		// save anything
493 		if(userDB instanceof XMLDatabase &&
494 				!((XMLDatabase)userDB).isDirty()){
495 			return;
496 		}
497 		
498 		try {
499 			if(sock != null && userDB != null){
500 
501 				// this is a hack to test this should be moved
502 				// to the converter
503 				//getWorkRim().setShape(bytes.getClass());
504 				
505 				if(getWorkRim().getShape() == byte[].class){
506 					ByteArrayOutputStream out = new ByteArrayOutputStream();
507 					viewerHelper.saveOTDatabase(userDB, out);
508 					byte [] bytes = out.toByteArray();
509 					sock.add(bytes, replace);
510 					if(userDB instanceof XMLDatabase){
511 						((XMLDatabase)userDB).setDirty(false);
512 					}
513 				} else {
514 					String userData = viewerHelper.saveOTDatabase(userDB);
515 					sock.add(userData, replace);
516 				}
517 				needsSockEntryInit = false;
518 			} else {
519 				System.err.println("No sock in OTrunkStep - cannot save data");
520 			}
521 			
522 		} catch (Exception e1) {
523 			// TODO Auto-generated catch block
524 			e1.printStackTrace();
525 		}										
526 
527 	}
528 
529 	/***
530 	 * @see OTPodAdapter.registerBean()
531 	 */
532 	@SuppressWarnings("unchecked")
533 	public void registerBean(Object bean) {
534 		getBeanContext().add(bean);
535 	}
536 
537 
538 	private String getAuthoredDataUrlStr() {
539 		String authoredDataUrlStr = getAuthoredDataURL().toExternalForm();
540 		
541 		// Figure out if there is a url being set through the configuration
542 		String authoredDataConfigUrlStr = 
543 			sessionService.getProperty(SAILOTRUNK_OTMLURL, null);
544 		
545 		// In the future we can load both the config url string
546 		// and the interally set (pod) url string. So the data can 
547 		// be split up.  For now we'll just replace the old string
548 		// with the new one
549 		if(authoredDataConfigUrlStr != null) {
550 			authoredDataUrlStr = authoredDataConfigUrlStr;
551 		}
552 		return authoredDataUrlStr;
553 	}
554 
555 	private XMLDatabase getLearnerDatabase() {
556 		
557 		ISock sock = getWorkSock();
558 		
559 		Object lastEntry = null;
560 		if(sock != null && sock.size() > 0){
561 			lastEntry = sock.peek();
562 		}
563 
564 		if(lastEntry == null){
565 			return null;
566 		}
567 		
568 		try {
569 			if(lastEntry instanceof String) {
570 				StringReader reader = new StringReader((String)lastEntry);
571 
572 				// FIXME need to pass in some context url this is needed
573 				// for any referenced resources in the otml
574 				return (XMLDatabase) viewerHelper.loadOTDatabase(reader, null);
575 
576 			} else if(lastEntry instanceof byte[]){
577 				ByteArrayInputStream inStream = new ByteArrayInputStream((byte[])lastEntry); 
578 
579 				return (XMLDatabase) viewerHelper.loadOTDatabase(inStream, null);
580 			}										
581 
582 		} catch (Exception exp) {
583 			exp.printStackTrace();
584 		}
585 		
586 		return null;
587 	}
588 	
589 }