From bf4b0d2bf08f8846709c51a4fa7e5c20dcd83840 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 May 2011 10:17:59 +0000 Subject: [PATCH] Maven structure for bbb-api-demo. Copied jsp files from last bbb-api-demo and bigbluebutton-web in different folders. Both has repeated names for jsp. --- bbb-api-demo/pom.xml | 21 + bbb-api-demo/src/main/webapp/WEB-INF/web.xml | 7 + .../src/main/webapp/api-demo/bbb_api.jsp | 579 ++++++++++++++++++ .../src/main/webapp/api-demo/bbb_api_conf.jsp | 14 + .../src/main/webapp/api-demo/demo5.jsp | 120 ++++ .../src/main/webapp/api-demo/demo6.jsp | 112 ++++ .../src/main/webapp/api-demo/demo_header.jsp | 1 + .../src/main/webapp/bbb-web/bbb_api.jsp | 448 ++++++++++++++ .../src/main/webapp/bbb-web/create.jsp | 308 ++++++++++ .../src/main/webapp/bbb-web/demo1.jsp | 119 ++++ .../src/main/webapp/bbb-web/demo2.jsp | 130 ++++ .../src/main/webapp/bbb-web/demo3.jsp | 293 +++++++++ .../src/main/webapp/bbb-web/demo4.jsp | 106 ++++ .../src/main/webapp/bbb-web/demo4_helper.jsp | 6 + .../src/main/webapp/bbb-web/demo5.jsp | 308 ++++++++++ .../src/main/webapp/bbb-web/demo6.jsp | 263 ++++++++ .../src/main/webapp/bbb-web/demo_footer.jsp | 2 + .../src/main/webapp/bbb-web/demo_header.jsp | 1 + .../src/main/webapp/bbb-web/error.jsp | 128 ++++ bbb-api-demo/src/main/webapp/index.jsp | 5 + bbb-api-demo/target/bbb-api-demo.war | Bin 0 -> 31674 bytes .../target/bbb-api-demo/WEB-INF/web.xml | 7 + .../target/bbb-api-demo/api-demo/bbb_api.jsp | 579 ++++++++++++++++++ .../bbb-api-demo/api-demo/bbb_api_conf.jsp | 14 + .../target/bbb-api-demo/api-demo/demo5.jsp | 120 ++++ .../target/bbb-api-demo/api-demo/demo6.jsp | 112 ++++ .../bbb-api-demo/api-demo/demo_header.jsp | 1 + .../target/bbb-api-demo/bbb-web/bbb_api.jsp | 448 ++++++++++++++ .../target/bbb-api-demo/bbb-web/create.jsp | 308 ++++++++++ .../target/bbb-api-demo/bbb-web/demo1.jsp | 119 ++++ .../target/bbb-api-demo/bbb-web/demo2.jsp | 130 ++++ .../target/bbb-api-demo/bbb-web/demo3.jsp | 293 +++++++++ .../target/bbb-api-demo/bbb-web/demo4.jsp | 106 ++++ .../bbb-api-demo/bbb-web/demo4_helper.jsp | 6 + .../target/bbb-api-demo/bbb-web/demo5.jsp | 308 ++++++++++ .../target/bbb-api-demo/bbb-web/demo6.jsp | 263 ++++++++ .../bbb-api-demo/bbb-web/demo_footer.jsp | 2 + .../bbb-api-demo/bbb-web/demo_header.jsp | 1 + .../target/bbb-api-demo/bbb-web/error.jsp | 128 ++++ bbb-api-demo/target/bbb-api-demo/index.jsp | 5 + .../target/maven-archiver/pom.properties | 5 + bbb-api-demo/target/war/work/webapp-cache.xml | 43 ++ 42 files changed, 5969 insertions(+) create mode 100644 bbb-api-demo/pom.xml create mode 100644 bbb-api-demo/src/main/webapp/WEB-INF/web.xml create mode 100755 bbb-api-demo/src/main/webapp/api-demo/bbb_api.jsp create mode 100755 bbb-api-demo/src/main/webapp/api-demo/bbb_api_conf.jsp create mode 100755 bbb-api-demo/src/main/webapp/api-demo/demo5.jsp create mode 100755 bbb-api-demo/src/main/webapp/api-demo/demo6.jsp create mode 100755 bbb-api-demo/src/main/webapp/api-demo/demo_header.jsp create mode 100644 bbb-api-demo/src/main/webapp/bbb-web/bbb_api.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/create.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo1.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo2.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo3.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo4.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo4_helper.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo5.jsp create mode 100644 bbb-api-demo/src/main/webapp/bbb-web/demo6.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo_footer.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/demo_header.jsp create mode 100755 bbb-api-demo/src/main/webapp/bbb-web/error.jsp create mode 100644 bbb-api-demo/src/main/webapp/index.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo.war create mode 100644 bbb-api-demo/target/bbb-api-demo/WEB-INF/web.xml create mode 100644 bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api_conf.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/api-demo/demo5.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/api-demo/demo6.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/api-demo/demo_header.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/bbb_api.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/create.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo1.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo2.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo3.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo4.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo4_helper.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo5.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo6.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo_footer.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/demo_header.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/bbb-web/error.jsp create mode 100644 bbb-api-demo/target/bbb-api-demo/index.jsp create mode 100644 bbb-api-demo/target/maven-archiver/pom.properties create mode 100644 bbb-api-demo/target/war/work/webapp-cache.xml diff --git a/bbb-api-demo/pom.xml b/bbb-api-demo/pom.xml new file mode 100644 index 0000000000..d6c285b34c --- /dev/null +++ b/bbb-api-demo/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + bigbluebutton + bbb-api-demo + war + 0.8-PRE + bbb-api-demo Maven Webapp + http://maven.apache.org + + + junit + junit + 3.8.1 + test + + + + bbb-api-demo + + diff --git a/bbb-api-demo/src/main/webapp/WEB-INF/web.xml b/bbb-api-demo/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..9f88c1f963 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,7 @@ + + + + Archetype Created Web Application + diff --git a/bbb-api-demo/src/main/webapp/api-demo/bbb_api.jsp b/bbb-api-demo/src/main/webapp/api-demo/bbb_api.jsp new file mode 100755 index 0000000000..92e21ff93e --- /dev/null +++ b/bbb-api-demo/src/main/webapp/api-demo/bbb_api.jsp @@ -0,0 +1,579 @@ +<%/* + BigBlueButton - http://www.bigbluebutton.org + + Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, If not, see . + + Author: Fred Dixon + */%> +<%@page import="javax.xml.transform.dom.DOMSource"%> +<%@page import="javax.xml.transform.stream.StreamResult"%> +<%@page import="javax.xml.transform.OutputKeys"%> +<%@page import="javax.xml.transform.TransformerFactory"%> +<%@page import="javax.xml.transform.Transformer"%> +<%@page import="org.w3c.dom.Element"%> +<%@page import="com.sun.org.apache.xerces.internal.dom.ChildNode"%> +<%@page import="org.w3c.dom.Node"%> +<%@page import="org.w3c.dom.NodeList"%> +<%@page import="java.util.*,java.io.*,java.net.*,javax.crypto.*,javax.xml.parsers.*,org.w3c.dom.Document,org.xml.sax.*" errorPage="error.jsp"%> + +<%@ page import="org.apache.commons.codec.digest.*"%> +<%@ page import="java.io.*"%> +<%@ page import="java.nio.channels.FileChannel"%> + +<%@ include file="bbb_api_conf.jsp"%> + +<%!// + // Create a meeting with specific + // - meetingID + // - welcome message + // - moderator password + // - viewer password + // - voiceBridge + // - logoutURL + // + public String createMeeting(String meetingID, String welcome, + String moderatorPassword, String viewerPassword, + Integer voiceBridge, String logoutURL) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String checksum = ""; + + String attendee_password_param = "&attendeePW=ap"; + String moderator_password_param = "&moderatorPW=mp"; + String voice_bridge_param = ""; + String logoutURL_param = ""; + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + if ((moderatorPassword != null) && !moderatorPassword.equals("")) { + moderator_password_param = "&moderatorPW=" + + urlEncode(moderatorPassword); + } + + if ((viewerPassword != null) && !viewerPassword.equals("")) { + attendee_password_param = "&attendeePW=" + + urlEncode(viewerPassword); + } + + if ((voiceBridge != null) && voiceBridge > 0) { + voice_bridge_param = "&voiceBridge=" + + urlEncode(voiceBridge.toString()); + } else { + // No voice bridge number passed, so we'll generate a random one for this meeting + Random random = new Random(); + Integer n = 70000 + random.nextInt(9999); + voice_bridge_param = "&voiceBridge=" + n; + } + + if ((logoutURL != null) && !logoutURL.equals("")) { + logoutURL_param = "&logoutURL=" + urlEncode(logoutURL); + } + + // + // Now create the URL + // + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + attendee_password_param + moderator_password_param + + voice_bridge_param + logoutURL_param; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt)); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + return meetingID; + } + + return "Error " + + doc.getElementsByTagName("messageKey").item(0) + .getTextContent().trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + // getJoinMeetingURL() -- get join meeting URL for both viewer and moderator + // + public String getJoinMeetingURL(String username, String meetingID, + String password) { + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=" + + urlEncode(password); + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + } + + // + // Create a meeting and return a URL to join it as moderator + // + public String getJoinURL(String username, String meetingID, String welcome) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String good_url = "xxx"; + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + // + // When creating a meeting, the 'name' parameter is the name of the meeting (not to be confused with + // the username). For example, the name could be "Fred's meeting" and the meetingID could be "ID-1234312". + // + // While name and meetinID could be different, we'll keep them the same. Why? Because calling api/create? + // with a previously used meetingID will return same meetingToken (regardless if the meeting is running or not). + // + // This means the first person to call getJoinURL with meetingID="Demo Meeting" will actually create the + // meeting. Subsequent calls will return the same meetingToken and thus subsequent users will join the same + // meeting. + // + // Note: We're hard-coding the password for moderator and attendee (viewer) for purposes of demo. + // + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + voiceBridge; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + good_url = base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt); + String xml = getURL( good_url ); + //String xml = getURL(base_url_create + create_parameters + /// + "&checksum=" + // + checksum("create" + create_parameters + salt)); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + // + // Now create a URL to join that meeting + // + + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent() + .trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + //Create a meeting and return a URL to join it as moderator + // + public String getJoinURLXML(String username, String meetingID, + String welcome, String xml_param) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + voiceBridge; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = excutePost(base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt), xml_param); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent() + .trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + // getJoinURLViewer() -- Get the URL to join a meeting as viewer + // + public String getJoinURLViewer(String username, String meetingID) { + + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=ap"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + } + + // + // checksum() -- create a hash based on the shared salt (located in bbb_api_conf.jsp) + // + public static String checksum(String s) { + String checksum = ""; + try { + checksum = org.apache.commons.codec.digest.DigestUtils.shaHex(s); + } catch (Exception e) { + e.printStackTrace(); + } + return checksum; + } + + // + // getURL() -- fetch a URL and return its contents as a String + // + public static String getURL(String url) { + StringBuffer response = null; + + try { + URL u = new URL(url); + HttpURLConnection httpConnection = (HttpURLConnection) u + .openConnection(); + + httpConnection.setUseCaches(false); + httpConnection.setDoOutput(true); + httpConnection.setRequestMethod("GET"); + + httpConnection.connect(); + int responseCode = httpConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStream input = httpConnection.getInputStream(); + + // Read server's response. + response = new StringBuffer(); + Reader reader = new InputStreamReader(input, "UTF-8"); + reader = new BufferedReader(reader); + char[] buffer = new char[1024]; + for (int n = 0; n >= 0;) { + n = reader.read(buffer, 0, buffer.length); + if (n > 0) + response.append(buffer, 0, n); + } + + input.close(); + httpConnection.disconnect(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (response != null) { + return response.toString(); + } else { + return ""; + } + } + + public static String excutePost(String targetURL, String urlParameters) + { + URL url; + HttpURLConnection connection = null; + int responseCode = 0; + try { + //Create connection + url = new URL(targetURL); + connection = (HttpURLConnection)url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", + "text/xml"); + + connection.setRequestProperty("Content-Length", "" + + Integer.toString(urlParameters.getBytes().length)); + connection.setRequestProperty("Content-Language", "en-US"); + + connection.setUseCaches (false); + connection.setDoInput(true); + connection.setDoOutput(true); + + //Send request + DataOutputStream wr = new DataOutputStream ( + connection.getOutputStream ()); + wr.writeBytes (urlParameters); + wr.flush (); + wr.close (); + + //Get Response + InputStream is = connection.getInputStream(); + BufferedReader rd = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuffer response = new StringBuffer(); + while((line = rd.readLine()) != null) { + response.append(line); + response.append('\r'); + } + rd.close(); + return response.toString(); + + } catch (Exception e) { + + e.printStackTrace(); + return null; + + } finally { + + if(connection != null) { + connection.disconnect(); + } + } + } + // + // getURLisMeetingRunning() -- return a URL that the client can use to poll for whether the given meeting is running + // + public String getURLisMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum = ""; + + try { + checksum = DigestUtils + .shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return base_url + base_main + "&checksum=" + checksum; + } + + // + // isMeetingRunning() -- check the BigBlueButton server to see if the meeting is running (i.e. there is someone in the meeting) + // + public String isMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum = ""; + + try { + checksum = DigestUtils + .shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return doc.getElementsByTagName("running").item(0).getTextContent() + .trim(); + } + + return "false"; + + } + + public String getMeetingInfoURL(String meetingID, String password) { + String meetingParameters = "meetingID=" + urlEncode(meetingID) + + "&password=" + password; + return BigBlueButtonURL + "api/getMeetingInfo?" + meetingParameters + + "&checksum=" + + checksum("getMeetingInfo" + meetingParameters + salt); + } + + public String getMeetingInfo(String meetingID, String password) { + try { + URLConnection hpCon = new URL( + getMeetingInfoURL(meetingID, password)).openConnection(); + + InputStreamReader isr = new InputStreamReader( + hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + return data; + } catch (Exception e) { + e.printStackTrace(System.out); + return ""; + } + } + + public String getMeetingsURL() { + String meetingParameters = "random=" + new Random().nextInt(9999); + return BigBlueButtonURL + "api/getMeetings?" + meetingParameters + + "&checksum=" + + checksum("getMeetings" + meetingParameters + salt); + } + + // + // Calls getMeetings to obtain the list of meetings, then calls getMeetingInfo for each meeting + // and concatenates the result. + // + public String getMeetings() { + try { + + // Call the API and get the result + URLConnection hpCon = new URL(getMeetingsURL()).openConnection(); + InputStreamReader isr = new InputStreamReader( + hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + Document doc = parseXml(data); + + // tags needed for parsing xml documents + final String startTag = ""; + final String endTag = ""; + final String startResponse = ""; + final String endResponse = ""; + + // if the request succeeded, then calculate the checksum of each meeting and insert it into the document + NodeList meetingsList = doc.getElementsByTagName("meeting"); + + String newXMldocument = startTag; + for (int i = 0; i < meetingsList.getLength(); i++) { + Element meeting = (Element) meetingsList.item(i); + String meetingID = meeting.getElementsByTagName("meetingID") + .item(0).getTextContent(); + String password = meeting.getElementsByTagName("moderatorPW") + .item(0).getTextContent(); + + data = getMeetingInfo(meetingID, password); + + if (data.indexOf("") != -1) { + int startIndex = data.indexOf(startResponse) + + startTag.length(); + int endIndex = data.indexOf(endResponse); + newXMldocument += "" + + data.substring(startIndex, endIndex) + + ""; + } + } + newXMldocument += endTag; + + return newXMldocument; + } catch (Exception e) { + e.printStackTrace(System.out); + return null; + } + } + + // + public String endMeeting(String meetingID, String moderatorPassword) { + + String base_main = "meetingID=" + urlEncode(meetingID) + "&password=" + + urlEncode(moderatorPassword); + String base_url = BigBlueButtonURL + "api/end?"; + String checksum = ""; + + try { + checksum = DigestUtils.shaHex("end" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return "true"; + } + + return "Error " + + doc.getElementsByTagName("messageKey").item(0) + .getTextContent().trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + + } + + // + // parseXml() -- return a DOM of the XML + // + public static Document parseXml(String xml) + throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory + .newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); + return doc; + } + + // + // urlEncode() -- URL encode the string + // + public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + %> diff --git a/bbb-api-demo/src/main/webapp/api-demo/bbb_api_conf.jsp b/bbb-api-demo/src/main/webapp/api-demo/bbb_api_conf.jsp new file mode 100755 index 0000000000..4612130975 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/api-demo/bbb_api_conf.jsp @@ -0,0 +1,14 @@ + +<%! +// This is the security salt that must match the value set in the BigBlueButton server +//String salt = "4951c2aea43e5af6d9598610b9e0b6c7"; +//String salt = "5e5ff0968546b8aaacce0462a99bca30"; +String salt = "5e5ff0968546b8aaacce0462a99bca30"; +// This is the URL for the BigBlueButton server 4951c2aea43e5af6d9598610b9e0b6c7 +String BigBlueButtonURL = "http://192.168.0.217/bigbluebutton/"; +%> + + + + + diff --git a/bbb-api-demo/src/main/webapp/api-demo/demo5.jsp b/bbb-api-demo/src/main/webapp/api-demo/demo5.jsp new file mode 100755 index 0000000000..bdb12b802b --- /dev/null +++ b/bbb-api-demo/src/main/webapp/api-demo/demo5.jsp @@ -0,0 +1,120 @@ + + Preupload Presentation

+ + <%@ include file="bbb_api.jsp"%> + <%@ include file="demo_header.jsp"%> + +

Demo #5: Upload a presentation before joining a Course...

+
+ + + + + + + + + + + + + + + + + + + + + + + +
 Full Name:  +
 Upload File:  +
    +
+ +
+ + + + <%@ page import="java.util.List" %> + <%@ page import="java.util.Iterator" %> + <%@ page import="java.io.File" %> + <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%> + <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%> + <%@ page import="org.apache.commons.fileupload.*"%> + <%@ page contentType="text/html;charset=UTF-8" language="java" %> + <%@page import="sun.security.provider.SHA"%> + <%@page import="org.apache.commons.codec.binary.Base64"%> + <%@page import="java.security.MessageDigest"%> + <% + String uname=""; + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + + if (!isMultipart) { + } + else { + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List items = null; + try { + items = upload.parseRequest(request); + } catch (FileUploadException e) { + e.printStackTrace(); + } + out.print(items.size()); + Iterator itr = items.iterator(); + while (itr.hasNext()) { + FileItem item = (FileItem) itr.next(); + String xml = null; + xml = " "; + if (item.isFormField()) + { + String name = item.getFieldName(); + String value = item.getString(); + if(name.equals("username")) + { + uname=value; + } + } else { + try { + + String itemName = item.getName(); + + if(itemName==""){ + xml = " "; + } + else { + byte[] b = item.get(); + String encoded = Base64.encodeBase64String(b); + xml = " "+encoded+"\" "; + } + } catch (Exception e) { + e.printStackTrace(); + } + + String joinURL = getJoinURLXML(uname, "Demo Meeting", "Presentation should be uploaded. It was uploaded as an encoded file", xml ); + if (joinURL.startsWith("http://")) { + %> +

Your presentation has been Uploaded

+ + + <% + } else { + %> + + Error: getJoinURL() failed +

+ <%=joinURL %> + <% + } + } + } + } + %> diff --git a/bbb-api-demo/src/main/webapp/api-demo/demo6.jsp b/bbb-api-demo/src/main/webapp/api-demo/demo6.jsp new file mode 100755 index 0000000000..1d820cb697 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/api-demo/demo6.jsp @@ -0,0 +1,112 @@ + + Preupload Presentation

+ + <%@ include file="bbb_api.jsp"%> + <%@ include file="demo_header.jsp"%> +<% +String fileURL = BigBlueButtonURL.replace("/bigbluebutton",":8080/demo"); +String name1="Demo123.pdf"; +String name2="Demo456.pdf"; +String name3="Demo789.pdf"; +%> + +

Demo #6: Send a presentation URL before joining a Course...

+
+ + + + + + + + + + + + + + + + + + + + + + + +
 Full Name:  +
 File Name:  +
    +
+ +
+ + + + <%@ page import="java.util.List" %> + <%@ page import="java.util.Iterator" %> + <%@ page import="java.io.File" %> + <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%> + <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%> + <%@ page import="org.apache.commons.fileupload.*"%> + <%@ page contentType="text/html;charset=UTF-8" language="java" %> + <%@page import="sun.security.provider.SHA"%> + <%@page import="org.apache.commons.codec.binary.Base64"%> + <%@page import="java.security.MessageDigest"%> + <% + + String uname=""; + String fname=""; + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + + if (!isMultipart) { + } + else { + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List items = null; + try { + items = upload.parseRequest(request); + } catch (FileUploadException e) { + e.printStackTrace(); + } + Iterator itr = items.iterator(); + String xml = null; + while (itr.hasNext()) { + FileItem item = (FileItem) itr.next(); + String name = item.getFieldName(); + String value = item.getString(); + if(name.equals("username")) { + uname=value; + } + if(name.equals("filename")) { + fname=value; + } + } + xml = " "; + String joinURL = getJoinURLXML(uname, "Demo Meeting", "Presentation URL should be passed.", xml ); + if (joinURL.startsWith("http://")) { + %> +

Your presentation URL has been passed

+ + + <% + } else { + %> + + Error: getJoinURL() failed +

+ <%=joinURL %> + <% + } +} +%> diff --git a/bbb-api-demo/src/main/webapp/api-demo/demo_header.jsp b/bbb-api-demo/src/main/webapp/api-demo/demo_header.jsp new file mode 100755 index 0000000000..b5f3b61f3d --- /dev/null +++ b/bbb-api-demo/src/main/webapp/api-demo/demo_header.jsp @@ -0,0 +1 @@ +
Auto Upload File | Auto Upload File URL diff --git a/bbb-api-demo/src/main/webapp/bbb-web/bbb_api.jsp b/bbb-api-demo/src/main/webapp/bbb-web/bbb_api.jsp new file mode 100644 index 0000000000..3b318275be --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/bbb_api.jsp @@ -0,0 +1,448 @@ +<% +/* + BigBlueButton - http://www.bigbluebutton.org + + Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, If not, see . + + Author: Fred Dixon +*/ +%> +<%@page import="javax.xml.transform.dom.DOMSource"%> +<%@page import="javax.xml.transform.stream.StreamResult"%> +<%@page import="javax.xml.transform.OutputKeys"%> +<%@page import="javax.xml.transform.TransformerFactory"%> +<%@page import="javax.xml.transform.Transformer"%> +<%@page import="org.w3c.dom.Element"%> +<%@page import="com.sun.org.apache.xerces.internal.dom.ChildNode"%> +<%@page import="org.w3c.dom.Node"%> +<%@page import="org.w3c.dom.NodeList"%> + +<%@ page + import="java.util.*,java.io.*,java.net.*,javax.crypto.*,javax.xml.parsers.*,org.w3c.dom.Document,org.xml.sax.*" + errorPage="error.jsp" %> + +<%@ page import="org.apache.commons.codec.digest.*"%> +<%@ include file="bbb_api_conf.jsp"%> + +<%! + +// +// Create a meeting with specific +// - meetingID +// - welcome message +// - moderator password +// - viewer password +// - voiceBridge +// - logoutURL +// +public String createMeeting(String meetingID, String welcome, String moderatorPassword, String viewerPassword, Integer voiceBridge, String logoutURL) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String checksum = ""; + + String attendee_password_param = "&attendeePW=ap"; + String moderator_password_param = "&moderatorPW=mp"; + String voice_bridge_param = ""; + String logoutURL_param = ""; + + if ( (welcome != null) && ! welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + if ( (moderatorPassword != null) && ! moderatorPassword.equals("")) { + moderator_password_param = "&moderatorPW=" + urlEncode(moderatorPassword); + } + + if ( (viewerPassword != null) && ! viewerPassword.equals("")) { + attendee_password_param = "&attendeePW=" + urlEncode(viewerPassword); + } + + if ( (voiceBridge != null) && voiceBridge > 0 ) { + voice_bridge_param = "&voiceBridge=" + urlEncode(voiceBridge.toString()); + } else { + // No voice bridge number passed, so we'll generate a random one for this meeting + Random random = new Random(); + Integer n = 70000 + random.nextInt(9999); + voice_bridge_param = "&voiceBridge=" + n; + } + + if ( (logoutURL != null) && ! logoutURL.equals("")) { + logoutURL_param = "&logoutURL=" + urlEncode(logoutURL); + } + + // + // Now create the URL + // + + String create_parameters = "name=" + urlEncode(meetingID) + "&meetingID=" + urlEncode(meetingID) + + welcome_param + attendee_password_param + moderator_password_param + + voice_bridge_param + logoutURL_param; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + "&checksum=" + checksum("create" + create_parameters + salt) ); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + + return meetingID; + } + + return "Error " + doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); +} + + + +// +// getJoinMeetingURL() -- get join meeting URL for both viewer and moderator +// +public String getJoinMeetingURL(String username, String meetingID, String password) { + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + + "&password=" + urlEncode(password); + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); +} + + + +// +// Create a meeting and return a URL to join it as moderator +// +public String getJoinURL(String username, String meetingID, String record, String welcome, Map metadata) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ( (welcome != null) && ! welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + // + // When creating a meeting, the 'name' parameter is the name of the meeting (not to be confused with + // the username). For example, the name could be "Fred's meeting" and the meetingID could be "ID-1234312". + // + // While name and meetinID could be different, we'll keep them the same. Why? Because calling api/create? + // with a previously used meetingID will return same meetingToken (regardless if the meeting is running or not). + // + // This means the first person to call getJoinURL with meetingID="Demo Meeting" will actually create the + // meeting. Subsequent calls will return the same meetingToken and thus subsequent users will join the same + // meeting. + // + // Note: We're hard-coding the password for moderator and attendee (viewer) for purposes of demo. + // + + String create_parameters = "name=" + urlEncode(meetingID) + "&meetingID=" + urlEncode(meetingID) + + welcome_param + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + + voiceBridge + "&record=" + record; + + if(metadata!=null){ + String metadata_params=""; + for(String metakey : metadata.keySet()){ + metadata_params = metadata_params + "&meta_" + urlEncode(metakey) + "=" + urlEncode(metadata.get(metakey)); + } + create_parameters = create_parameters + metadata_params; + } + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + "&checksum=" + checksum("create" + create_parameters + salt) ); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + + // + // Now create a URL to join that meeting + // + + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); +} + + +// +// getJoinURLViewer() -- Get the URL to join a meeting as viewer +// +public String getJoinURLViewer(String username, String meetingID) { + + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + + "&password=ap"; + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); +} + + + +// +// checksum() -- create a hash based on the shared salt (located in bbb_api_conf.jsp) +// +public static String checksum(String s) { + String checksum = ""; + try { + checksum = org.apache.commons.codec.digest.DigestUtils.shaHex(s); + } catch (Exception e) { + e.printStackTrace(); + } + return checksum; +} + +// +// getURL() -- fetch a URL and return its contents as a String +// +public static String getURL(String url) { + StringBuffer response = null; + + try { + URL u = new URL(url); + HttpURLConnection httpConnection = (HttpURLConnection) u + .openConnection(); + + httpConnection.setUseCaches(false); + httpConnection.setDoOutput(true); + httpConnection.setRequestMethod("GET"); + + httpConnection.connect(); + int responseCode = httpConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStream input = httpConnection.getInputStream(); + + // Read server's response. + response = new StringBuffer(); + Reader reader = new InputStreamReader(input, "UTF-8"); + reader = new BufferedReader(reader); + char[] buffer = new char[1024]; + for (int n = 0; n >= 0;) { + n = reader.read(buffer, 0, buffer.length); + if (n > 0) + response.append(buffer, 0, n); + } + + input.close(); + httpConnection.disconnect(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (response != null) { + return response.toString(); + } else { + return ""; + } +} + +// +// getURLisMeetingRunning() -- return a URL that the client can use to poll for whether the given meeting is running +// +public String getURLisMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return base_url + base_main + "&checksum=" + checksum; +} + +// +// isMeetingRunning() -- check the BigBlueButton server to see if the meeting is running (i.e. there is someone in the meeting) +// +public String isMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return doc.getElementsByTagName("running").item(0).getTextContent().trim(); + } + + return "false"; + +} + +public String getMeetingInfoURL(String meetingID, String password) { + String meetingParameters = "meetingID=" + urlEncode(meetingID) + "&password=" + password; + return BigBlueButtonURL + "api/getMeetingInfo?" + meetingParameters + "&checksum=" + checksum("getMeetingInfo" + meetingParameters + salt); +} + +public String getMeetingInfo(String meetingID, String password) { + try { + URLConnection hpCon = new URL(getMeetingInfoURL(meetingID, password)).openConnection(); + + InputStreamReader isr = new InputStreamReader(hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + return data; + } catch (Exception e) { + e.printStackTrace(System.out); + return ""; + } +} + +public String getMeetingsURL() { + String meetingParameters = "random=" + new Random().nextInt(9999); + return BigBlueButtonURL + "api/getMeetings?" + meetingParameters + "&checksum=" + checksum("getMeetings" + meetingParameters + salt); +} + +// +// Calls getMeetings to obtain the list of meetings, then calls getMeetingInfo for each meeting +// and concatenates the result. +// +public String getMeetings() { + try { + + // Call the API and get the result + URLConnection hpCon = new URL(getMeetingsURL()).openConnection(); + InputStreamReader isr = new InputStreamReader(hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + Document doc = parseXml(data); + + // tags needed for parsing xml documents + final String startTag = ""; + final String endTag = ""; + final String startResponse = ""; + final String endResponse = ""; + + // if the request succeeded, then calculate the checksum of each meeting and insert it into the document + NodeList meetingsList = doc.getElementsByTagName("meeting"); + + String newXMldocument = startTag; + for (int i = 0; i < meetingsList.getLength(); i++) { + Element meeting = (Element) meetingsList.item(i); + String meetingID = meeting.getElementsByTagName("meetingID").item(0).getTextContent(); + String password = meeting.getElementsByTagName("moderatorPW").item(0).getTextContent(); + + data = getMeetingInfo(meetingID, password); + + if (data.indexOf("") != -1) { + int startIndex = data.indexOf(startResponse) + startTag.length(); + int endIndex = data.indexOf(endResponse); + newXMldocument += "" + data.substring(startIndex, endIndex) + ""; + } + } + newXMldocument += endTag; + + return newXMldocument; + } catch (Exception e) { + e.printStackTrace(System.out); + return null; + } +} + +// +public String endMeeting(String meetingID, String moderatorPassword) { + + String base_main = "meetingID=" + urlEncode(meetingID) + "&password=" + urlEncode(moderatorPassword); + String base_url = BigBlueButtonURL + "api/end?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("end" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return "true"; + } + + return "Error " + doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); + +} + +// +// parseXml() -- return a DOM of the XML +// +public static Document parseXml(String xml) throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); + return doc; +} + +// +// urlEncode() -- URL encode the string +// +public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; +} +%> diff --git a/bbb-api-demo/src/main/webapp/bbb-web/create.jsp b/bbb-api-demo/src/main/webapp/bbb-web/create.jsp new file mode 100755 index 0000000000..2e73d40db8 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/create.jsp @@ -0,0 +1,308 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Create Your Own Meeting + + + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +<%@ include file="demo_header.jsp"%> +

Create Your Own Meeting

+ +

+

+ + + + + + + + +
Create your own meeting. +

+

Step 1. Enter your name:
+ + +
+
+ +
+ + + +<% + } else if (request.getParameter("action").equals("create")) { + // + // User has requested to create a meeting + // + + String username = request.getParameter("username1"); + String meetingID = username + "'s meeting"; + String record = request.getParameter("record1"); + + String meeting_ID = ""; + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, meetingID, record, "
Welcome to %%CONFNAME%%.
", null); + + + String inviteURL = BigBlueButtonURL + "demo/create.jsp?action=invite&meetingID=" + URLEncoder.encode(meetingID, "UTF-8"); +%> + +
+

Meeting Created

+
+ + + + + + + + + + +
+
<%=username%>'s meeting has been + created.
+
+

 

+ + Step 2. Invite others using the following link (shown below): +
+ +
+

  +

Step 3. Click the following link to start your meeting: +

 

+
Start Meeting
+

 

+ +
+ + + + + + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String meetingID = request.getParameter("meetingID"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/create.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&meetingID=" + + URLEncoder.encode(meetingID, "UTF-8"); + + if (isMeetingRunning(meetingID).equals("true")) { + // + // The meeting has started -- bring the user into the meeting. + // +%> + +<% + } else { + // + // The meeting has not yet started, so check until we get back the status that the meeting is running + // + String checkMeetingStatus = getURLisMeetingRunning(meetingID); +%> + + + +
+

<%=meetingID%> has not yet started.

+
+ + + + + + + + + +
+ +

Hi <%=username%>,

+

Now waiting for the moderator to start <%=meetingID%>.

+
+

(Your browser will automatically refresh and join the meeting + when it starts.)

+
+ + +<% +} + } else if (request.getParameter("action").equals("invite")) { + // + // We have an invite to an active meeting. Ask the person for their name + // so they can join. + // + String meetingID = request.getParameter("meetingID"); +%> + +
+

Invite

+
+ +
+ + + + + + + + + +
+ +

You have been invited to join
+ <%=meetingID%>. +

Enter your name:
+
+
+ +
+ + + + +<% + } else if (request.getParameter("action").equals("join")) { + // + // We have an invite request to join an existing meeting and the meeting is running + // + // We don't need to pass a meeting descritpion as it's already been set by the first time + // the meeting was created. + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("meetingID")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo1.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo1.jsp new file mode 100755 index 0000000000..1d8f36a620 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo1.jsp @@ -0,0 +1,119 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + Join a Course + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% +if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // + %> +<%@ include file="demo_header.jsp"%> + +

Demo #1: Join a Course

+ + +
+ + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   +   +   +
+ +
+ + +<% +} else if (request.getParameter("action").equals("create")) { + + // + // Got an action=create + // + + // + // Request a URL to join a meeting called "Demo Meeting" + // Pass null for welcome message to use the default message (see defaultWelcomeMessage in bigbluebutton.properties) + // Update: Added record parameter, default: false + // + String joinURL = getJoinURL(request.getParameter("username"), "Demo Meeting", "false", null, null); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

+<%=joinURL %> + +<% + } +} +%> + + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo2.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo2.jsp new file mode 100755 index 0000000000..e1bc45a47a --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo2.jsp @@ -0,0 +1,130 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + +Join a Selected Course + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> + +<%@ include file="demo_header.jsp"%> + +

Demo #2: Join a Selected Course

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   + Course: +   + + +
+   +   +   +
+ +
+ +<% + } else if (request.getParameter("action").equals("create")) { + // + // Got an action=create + // + + String username = request.getParameter("username"); + String meetingID = request.getParameter("meetingID"); + + // String joinURL = getJoinURL(username, meetingID, "Welcome to " + meetingID ); + // Update: added record parameter, default false + String joinURL = getJoinURL(username, meetingID,"false", "
Welcome to course: %%CONFNAME%%.
", null ); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> <% + } + } + %> <%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo3.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo3.jsp new file mode 100755 index 0000000000..457ebbcc96 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo3.jsp @@ -0,0 +1,293 @@ + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + Join a Course (Password Required) + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% + +// +// We're going to define some sample courses (meetings) below. This API exampe shows how you can create a login page for a course. +// The password below are not available to users as they are compiled on the server. +// + +HashMap allMeetings = new HashMap(); +HashMap meeting; + +String welcome = "
Welcome to %%CONFNAME%%!

For help see our tutorial videos.

To join the voice bridge for this meeting:
(1) click the headset icon in the upper-left, or
(2) dial xxx-xxx-xxxx (toll free:1-xxx-xxx-xxxx) and enter conference ID: %%CONFNUM%%.

"; + + +// +// English courses +// + +meeting = new HashMap(); +allMeetings.put( "ENGL-2013: Research Methods in English", meeting ); // The title that will appear in the drop-down menu + meeting.put("welcomeMsg", welcome); // The welcome mesage + meeting.put("moderatorPW", "prof123"); // The password for moderator + meeting.put("viewerPW", "student123"); // The password for viewer + meeting.put("voiceBridge", "72013"); // The extension number for the voice bridge (use if connected to phone system) + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); // The logout URL (use if you want to return to your pages) + +meeting = new HashMap(); +allMeetings.put( "ENGL-2213: Drama Production I", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72213"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "ENGL-2023: Survey of English Literature", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72023"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +// +// Law Courses +// + +meeting = new HashMap(); +allMeetings.put( "LAW-1323: Fundamentals of Advocacy ", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "71232"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "LAW-2273: Business Organizations", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72273"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "LAW-3113: Corporate Finance", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "theprof"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "71642"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + + +// +// Professor's Virtaul Office Hours +// + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Steve Stoyan", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70001"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Michael Bailetti", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70002"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Tony Weiss", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70003"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + + +meeting = null; + +Iterator meetingIterator = new TreeSet(allMeetings.keySet()).iterator(); + +if (request.getParameterMap().isEmpty()) { + // + // Assume we want to join a course + // + %> +<%@ include file="demo_header.jsp"%> + +

Demo #3: Join a Course (password required)

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   + Course: +   + + + +
+   + Password: +   +
+   +   +   +
+ +
+ +Passwords: +
    +
  • prof123 - login as professor (moderator privlidges)
  • +
  • student123 - login as student (viewer privlidges)
  • +
+ + +<% + } else if (request.getParameter("action").equals("create")) { + // + // Got an action=create + // + + String username = request.getParameter("username"); + String meetingID = request.getParameter("meetingID"); + String password = request.getParameter("password"); + + meeting = allMeetings.get( meetingID ); + + String welcomeMsg = meeting.get( "welcomeMsg" ); + String logoutURL = meeting.get( "logoutURL" ); + Integer voiceBridge = Integer.parseInt( meeting.get( "voiceBridge" ).trim() ); + + String viewerPW = meeting.get( "viewerPW" ); + String moderatorPW = meeting.get( "moderatorPW" ); + + // + // Check if we have a valid password + // + if ( ! password.equals(viewerPW) && ! password.equals(moderatorPW) ) { +%> + +Invalid Password, please try again. + +<% + return; + } + + // + // Looks good, let's create the meeting + // + String meeting_ID = createMeeting( meetingID, welcomeMsg, moderatorPW, viewerPW, voiceBridge, logoutURL ); + + // + // Check if we have an error. + // + if( meeting_ID.startsWith("Error ")) { +%> + +Error: createMeeting() failed +

<%=meeting_ID%> + + +<% + return; + } + + // + // We've got a valid meeting_ID and passoword -- let's join! + // + + String joinURL = getJoinMeetingURL(username, meeting_ID, password); +%> + + + +<% + } +%> + +<%@ include file="demo_footer.jsp"%> + + + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo4.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo4.jsp new file mode 100755 index 0000000000..edee4c1e9b --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo4.jsp @@ -0,0 +1,106 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + +<%@page import="org.w3c.dom.*"%> + + + + +Activity Monitor + + + + + + + + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<%@ include file="demo_header.jsp"%> + + +<% +if (request.getParameterMap().isEmpty()) { +%> + +

Demo #4: Activity Monitor

+ +

+ +
+ + +<% +} else if (request.getParameter("action").equals("end")) { + + String mp = request.getParameter("moderatorPW"); + String meetingID = request.getParameter("meetingID"); + + String result = endMeeting(meetingID, mp); + + if ( result.equals("true") ){ + +%> + +

Demo #4: Activity Monitor

+ +<%=meetingID%> has been terminated. + +

+ +
+ +<% } else { %> + +

Demo #4: Activity Monitor

+ + +Unable to end meeting: <%=meetingID%> + +<%=result%> + + + + +<% } + }%> + + <%@ include file="demo_footer.jsp"%> + + + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo4_helper.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo4_helper.jsp new file mode 100755 index 0000000000..6532a44886 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo4_helper.jsp @@ -0,0 +1,6 @@ + +<%= getMeetings() %> +<%@ include file="bbb_api.jsp" %> +<%@ page contentType="text/xml" %> + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo5.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo5.jsp new file mode 100755 index 0000000000..f0251ab2f2 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo5.jsp @@ -0,0 +1,308 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Create Your Own Meeting + + + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +<%@ include file="demo_header.jsp"%> +

Create Your Own Meeting

+ +

+

+ + + + + + + + +
Create your own meeting. +

+

Step 1. Enter your name:
+ + +
+
+ +
+ + + +<% + } else if (request.getParameter("action").equals("create")) { + // + // User has requested to create a meeting + // + + String username = request.getParameter("username1"); + String meetingID = username + "'s meeting"; + String record = request.getParameter("record1"); + + String meeting_ID = ""; + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, meetingID, record, "
Welcome to %%CONFNAME%%.
", null); + + + String inviteURL = BigBlueButtonURL + "demo/demo5.jsp?action=invite&meetingID=" + URLEncoder.encode(meetingID, "UTF-8"); +%> + +
+

Meeting Created

+
+ + + + + + + + + + +
+
<%=username%>'s meeting has been + created.
+
+

 

+ + Step 2. Invite others using the following link (shown below): +
+ +
+

  +

Step 3. Click the following link to start your meeting: +

 

+
Start Meeting
+

 

+ +
+ + + + + + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String meetingID = request.getParameter("meetingID"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/demo5.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&meetingID=" + + URLEncoder.encode(meetingID, "UTF-8"); + + if (isMeetingRunning(meetingID).equals("true")) { + // + // The meeting has started -- bring the user into the meeting. + // +%> + +<% + } else { + // + // The meeting has not yet started, so check until we get back the status that the meeting is running + // + String checkMeetingStatus = getURLisMeetingRunning(meetingID); +%> + + + +
+

<%=meetingID%> has not yet started.

+
+ + + + + + + + + +
+ +

Hi <%=username%>,

+

Now waiting for the moderator to start <%=meetingID%>.

+
+

(Your browser will automatically refresh and join the meeting + when it starts.)

+
+ + +<% +} + } else if (request.getParameter("action").equals("invite")) { + // + // We have an invite to an active meeting. Ask the person for their name + // so they can join. + // + String meetingID = request.getParameter("meetingID"); +%> + +
+

Invite

+
+ +
+ + + + + + + + + +
+ +

You have been invited to join
+ <%=meetingID%>. +

Enter your name:
+
+
+ +
+ + + + +<% + } else if (request.getParameter("action").equals("join")) { + // + // We have an invite request to join an existing meeting and the meeting is running + // + // We don't need to pass a meeting descritpion as it's already been set by the first time + // the meeting was created. + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("meetingID")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo6.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo6.jsp new file mode 100644 index 0000000000..c784673d45 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo6.jsp @@ -0,0 +1,263 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Recording Meeting Demo + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +<%@ include file="demo_header.jsp"%> + +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +

Demo Recording

+ +
+
+ Meeting Information +
    +
  • + + +
  • +
  • + + +
  • +
+
+
+ Metadata Details +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+ + +
+ +<% + } else if (request.getParameter("action").equals("create")) { + + String confname=request.getParameter("confname"); + String username = request.getParameter("username1"); + + //metadata + Map metadata=new HashMap(); + + metadata.put("title",request.getParameter("meta_title")); + metadata.put("subject",request.getParameter("meta_subject")); + metadata.put("description",request.getParameter("meta_description")); + metadata.put("creator",request.getParameter("meta_creator")); + metadata.put("contributor",request.getParameter("meta_contributor")); + metadata.put("language",request.getParameter("meta_language")); + metadata.put("identifier",request.getParameter("meta_identifier")); + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, confname, "true", null, metadata); + + + String inviteURL = BigBlueButtonURL + "demo/demo6.jsp?action=invite&confname=" + URLEncoder.encode(confname, "UTF-8"); +%> + + +

Session Created

+ + + + +Start Session + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String confname = request.getParameter("confname"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/demo6.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&confname=" + + URLEncoder.encode(confname, "UTF-8"); + + if (isMeetingRunning(confname).equals("true")) { + +%> + +<% + } else { + + String checkMeetingStatus = getURLisMeetingRunning(confname); +%> + + + +

<%=confname%> has not yet started.

+ + + + +<% +} + } else if (request.getParameter("action").equals("invite")) { + + String meetingID = request.getParameter("confname"); +%> + +

Invite

+ +
+ + + + + + + + +
+ + +<% + } else if (request.getParameter("action").equals("join")) { + + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("confname")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo_footer.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo_footer.jsp new file mode 100755 index 0000000000..d142598dca --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo_footer.jsp @@ -0,0 +1,2 @@ +

+These demos use the BigBlueButton API. The source code for these demos is available here. diff --git a/bbb-api-demo/src/main/webapp/bbb-web/demo_header.jsp b/bbb-api-demo/src/main/webapp/bbb-web/demo_header.jsp new file mode 100755 index 0000000000..de34d86426 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/demo_header.jsp @@ -0,0 +1 @@ +
Join a Course | Join a Selected Course | Join a Course (password required) | Activity Monitor | Create Your Own Meeting | Record Meeting diff --git a/bbb-api-demo/src/main/webapp/bbb-web/error.jsp b/bbb-api-demo/src/main/webapp/bbb-web/error.jsp new file mode 100755 index 0000000000..364fcdbf61 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/bbb-web/error.jsp @@ -0,0 +1,128 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + +<%@ page isErrorPage="true" %> +<%@ page language="java" %> +<%@ page import="java.util.*" %> +<%@ page import="java.io.*" %> + +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + Object statusCode = request.getAttribute("javax.servlet.error.status_code"); + Object exceptionType = request.getAttribute("javax.servlet.error.exception_type"); + Object message = request.getAttribute("javax.servlet.error.message"); +%> + + + +Error Page + + + +

Home

+
+

An Error has occured:

+ + + + + + + + + + + + + + + + + + + + + + +
Status Code<%= statusCode %>
Exception Type<%= exceptionType %>
Message<%= message %>
Exception + <% + if( exception != null ) + { + out.print("
");
+		    exception.printStackTrace(new PrintWriter(out));
+		    out.print("
"); + } + %> +
Root Cause + <% + if( (exception != null) && (exception instanceof ServletException) ) + { + Throwable cause = ((ServletException)exception).getRootCause(); + if( cause != null ) + { + out.print("
");
+			cause.printStackTrace(new PrintWriter(out));
+			out.print("
"); + } + } + %> +
+ +
+Header List + + + + + +<% +String name = ""; +String value = ""; + +java.util.Enumeration headers = request.getHeaderNames(); +while(headers.hasMoreElements()) +{ + name = (String) headers.nextElement(); + value = request.getHeader(name); +%> + + + + +<% +} +%> +
NameValue
<%=name%><%=value%>
+ +Attribute List + + +<% +java.util.Enumeration attributes = request.getAttributeNames(); +while(attributes.hasMoreElements()) +{ + name = (String) attributes.nextElement(); + + if (request.getAttribute(name) == null) + { + value = "null"; + } + else + { + value = request.getAttribute(name).toString(); + } +%> + + + + +<% +} +%> +
<%=name%><%=value%>
+ + + \ No newline at end of file diff --git a/bbb-api-demo/src/main/webapp/index.jsp b/bbb-api-demo/src/main/webapp/index.jsp new file mode 100644 index 0000000000..c38169bb95 --- /dev/null +++ b/bbb-api-demo/src/main/webapp/index.jsp @@ -0,0 +1,5 @@ + + +

Hello World!

+ + diff --git a/bbb-api-demo/target/bbb-api-demo.war b/bbb-api-demo/target/bbb-api-demo.war new file mode 100644 index 0000000000000000000000000000000000000000..e23f32a2fc20be723503dbdb4e9f3a8878740390 GIT binary patch literal 31674 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 literal 0 HcmV?d00001 diff --git a/bbb-api-demo/target/bbb-api-demo/WEB-INF/web.xml b/bbb-api-demo/target/bbb-api-demo/WEB-INF/web.xml new file mode 100644 index 0000000000..9f88c1f963 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/WEB-INF/web.xml @@ -0,0 +1,7 @@ + + + + Archetype Created Web Application + diff --git a/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api.jsp b/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api.jsp new file mode 100644 index 0000000000..92e21ff93e --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api.jsp @@ -0,0 +1,579 @@ +<%/* + BigBlueButton - http://www.bigbluebutton.org + + Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, If not, see . + + Author: Fred Dixon + */%> +<%@page import="javax.xml.transform.dom.DOMSource"%> +<%@page import="javax.xml.transform.stream.StreamResult"%> +<%@page import="javax.xml.transform.OutputKeys"%> +<%@page import="javax.xml.transform.TransformerFactory"%> +<%@page import="javax.xml.transform.Transformer"%> +<%@page import="org.w3c.dom.Element"%> +<%@page import="com.sun.org.apache.xerces.internal.dom.ChildNode"%> +<%@page import="org.w3c.dom.Node"%> +<%@page import="org.w3c.dom.NodeList"%> +<%@page import="java.util.*,java.io.*,java.net.*,javax.crypto.*,javax.xml.parsers.*,org.w3c.dom.Document,org.xml.sax.*" errorPage="error.jsp"%> + +<%@ page import="org.apache.commons.codec.digest.*"%> +<%@ page import="java.io.*"%> +<%@ page import="java.nio.channels.FileChannel"%> + +<%@ include file="bbb_api_conf.jsp"%> + +<%!// + // Create a meeting with specific + // - meetingID + // - welcome message + // - moderator password + // - viewer password + // - voiceBridge + // - logoutURL + // + public String createMeeting(String meetingID, String welcome, + String moderatorPassword, String viewerPassword, + Integer voiceBridge, String logoutURL) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String checksum = ""; + + String attendee_password_param = "&attendeePW=ap"; + String moderator_password_param = "&moderatorPW=mp"; + String voice_bridge_param = ""; + String logoutURL_param = ""; + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + if ((moderatorPassword != null) && !moderatorPassword.equals("")) { + moderator_password_param = "&moderatorPW=" + + urlEncode(moderatorPassword); + } + + if ((viewerPassword != null) && !viewerPassword.equals("")) { + attendee_password_param = "&attendeePW=" + + urlEncode(viewerPassword); + } + + if ((voiceBridge != null) && voiceBridge > 0) { + voice_bridge_param = "&voiceBridge=" + + urlEncode(voiceBridge.toString()); + } else { + // No voice bridge number passed, so we'll generate a random one for this meeting + Random random = new Random(); + Integer n = 70000 + random.nextInt(9999); + voice_bridge_param = "&voiceBridge=" + n; + } + + if ((logoutURL != null) && !logoutURL.equals("")) { + logoutURL_param = "&logoutURL=" + urlEncode(logoutURL); + } + + // + // Now create the URL + // + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + attendee_password_param + moderator_password_param + + voice_bridge_param + logoutURL_param; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt)); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + return meetingID; + } + + return "Error " + + doc.getElementsByTagName("messageKey").item(0) + .getTextContent().trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + // getJoinMeetingURL() -- get join meeting URL for both viewer and moderator + // + public String getJoinMeetingURL(String username, String meetingID, + String password) { + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=" + + urlEncode(password); + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + } + + // + // Create a meeting and return a URL to join it as moderator + // + public String getJoinURL(String username, String meetingID, String welcome) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String good_url = "xxx"; + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + // + // When creating a meeting, the 'name' parameter is the name of the meeting (not to be confused with + // the username). For example, the name could be "Fred's meeting" and the meetingID could be "ID-1234312". + // + // While name and meetinID could be different, we'll keep them the same. Why? Because calling api/create? + // with a previously used meetingID will return same meetingToken (regardless if the meeting is running or not). + // + // This means the first person to call getJoinURL with meetingID="Demo Meeting" will actually create the + // meeting. Subsequent calls will return the same meetingToken and thus subsequent users will join the same + // meeting. + // + // Note: We're hard-coding the password for moderator and attendee (viewer) for purposes of demo. + // + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + voiceBridge; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + good_url = base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt); + String xml = getURL( good_url ); + //String xml = getURL(base_url_create + create_parameters + /// + "&checksum=" + // + checksum("create" + create_parameters + salt)); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + // + // Now create a URL to join that meeting + // + + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent() + .trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + //Create a meeting and return a URL to join it as moderator + // + public String getJoinURLXML(String username, String meetingID, + String welcome, String xml_param) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ((welcome != null) && !welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + String create_parameters = "name=" + urlEncode(meetingID) + + "&meetingID=" + urlEncode(meetingID) + welcome_param + + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + voiceBridge; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = excutePost(base_url_create + create_parameters + + "&checksum=" + + checksum("create" + create_parameters + salt), xml_param); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent() + .trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + } + + // + // getJoinURLViewer() -- Get the URL to join a meeting as viewer + // + public String getJoinURLViewer(String username, String meetingID) { + + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + + "&fullName=" + urlEncode(username) + "&password=ap"; + + return base_url_join + join_parameters + "&checksum=" + + checksum("join" + join_parameters + salt); + } + + // + // checksum() -- create a hash based on the shared salt (located in bbb_api_conf.jsp) + // + public static String checksum(String s) { + String checksum = ""; + try { + checksum = org.apache.commons.codec.digest.DigestUtils.shaHex(s); + } catch (Exception e) { + e.printStackTrace(); + } + return checksum; + } + + // + // getURL() -- fetch a URL and return its contents as a String + // + public static String getURL(String url) { + StringBuffer response = null; + + try { + URL u = new URL(url); + HttpURLConnection httpConnection = (HttpURLConnection) u + .openConnection(); + + httpConnection.setUseCaches(false); + httpConnection.setDoOutput(true); + httpConnection.setRequestMethod("GET"); + + httpConnection.connect(); + int responseCode = httpConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStream input = httpConnection.getInputStream(); + + // Read server's response. + response = new StringBuffer(); + Reader reader = new InputStreamReader(input, "UTF-8"); + reader = new BufferedReader(reader); + char[] buffer = new char[1024]; + for (int n = 0; n >= 0;) { + n = reader.read(buffer, 0, buffer.length); + if (n > 0) + response.append(buffer, 0, n); + } + + input.close(); + httpConnection.disconnect(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (response != null) { + return response.toString(); + } else { + return ""; + } + } + + public static String excutePost(String targetURL, String urlParameters) + { + URL url; + HttpURLConnection connection = null; + int responseCode = 0; + try { + //Create connection + url = new URL(targetURL); + connection = (HttpURLConnection)url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", + "text/xml"); + + connection.setRequestProperty("Content-Length", "" + + Integer.toString(urlParameters.getBytes().length)); + connection.setRequestProperty("Content-Language", "en-US"); + + connection.setUseCaches (false); + connection.setDoInput(true); + connection.setDoOutput(true); + + //Send request + DataOutputStream wr = new DataOutputStream ( + connection.getOutputStream ()); + wr.writeBytes (urlParameters); + wr.flush (); + wr.close (); + + //Get Response + InputStream is = connection.getInputStream(); + BufferedReader rd = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuffer response = new StringBuffer(); + while((line = rd.readLine()) != null) { + response.append(line); + response.append('\r'); + } + rd.close(); + return response.toString(); + + } catch (Exception e) { + + e.printStackTrace(); + return null; + + } finally { + + if(connection != null) { + connection.disconnect(); + } + } + } + // + // getURLisMeetingRunning() -- return a URL that the client can use to poll for whether the given meeting is running + // + public String getURLisMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum = ""; + + try { + checksum = DigestUtils + .shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return base_url + base_main + "&checksum=" + checksum; + } + + // + // isMeetingRunning() -- check the BigBlueButton server to see if the meeting is running (i.e. there is someone in the meeting) + // + public String isMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum = ""; + + try { + checksum = DigestUtils + .shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return doc.getElementsByTagName("running").item(0).getTextContent() + .trim(); + } + + return "false"; + + } + + public String getMeetingInfoURL(String meetingID, String password) { + String meetingParameters = "meetingID=" + urlEncode(meetingID) + + "&password=" + password; + return BigBlueButtonURL + "api/getMeetingInfo?" + meetingParameters + + "&checksum=" + + checksum("getMeetingInfo" + meetingParameters + salt); + } + + public String getMeetingInfo(String meetingID, String password) { + try { + URLConnection hpCon = new URL( + getMeetingInfoURL(meetingID, password)).openConnection(); + + InputStreamReader isr = new InputStreamReader( + hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + return data; + } catch (Exception e) { + e.printStackTrace(System.out); + return ""; + } + } + + public String getMeetingsURL() { + String meetingParameters = "random=" + new Random().nextInt(9999); + return BigBlueButtonURL + "api/getMeetings?" + meetingParameters + + "&checksum=" + + checksum("getMeetings" + meetingParameters + salt); + } + + // + // Calls getMeetings to obtain the list of meetings, then calls getMeetingInfo for each meeting + // and concatenates the result. + // + public String getMeetings() { + try { + + // Call the API and get the result + URLConnection hpCon = new URL(getMeetingsURL()).openConnection(); + InputStreamReader isr = new InputStreamReader( + hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + Document doc = parseXml(data); + + // tags needed for parsing xml documents + final String startTag = ""; + final String endTag = ""; + final String startResponse = ""; + final String endResponse = ""; + + // if the request succeeded, then calculate the checksum of each meeting and insert it into the document + NodeList meetingsList = doc.getElementsByTagName("meeting"); + + String newXMldocument = startTag; + for (int i = 0; i < meetingsList.getLength(); i++) { + Element meeting = (Element) meetingsList.item(i); + String meetingID = meeting.getElementsByTagName("meetingID") + .item(0).getTextContent(); + String password = meeting.getElementsByTagName("moderatorPW") + .item(0).getTextContent(); + + data = getMeetingInfo(meetingID, password); + + if (data.indexOf("") != -1) { + int startIndex = data.indexOf(startResponse) + + startTag.length(); + int endIndex = data.indexOf(endResponse); + newXMldocument += "" + + data.substring(startIndex, endIndex) + + ""; + } + } + newXMldocument += endTag; + + return newXMldocument; + } catch (Exception e) { + e.printStackTrace(System.out); + return null; + } + } + + // + public String endMeeting(String meetingID, String moderatorPassword) { + + String base_main = "meetingID=" + urlEncode(meetingID) + "&password=" + + urlEncode(moderatorPassword); + String base_url = BigBlueButtonURL + "api/end?"; + String checksum = ""; + + try { + checksum = DigestUtils.shaHex("end" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return "true"; + } + + return "Error " + + doc.getElementsByTagName("messageKey").item(0) + .getTextContent().trim() + + ": " + + doc.getElementsByTagName("message").item(0).getTextContent() + .trim(); + + } + + // + // parseXml() -- return a DOM of the XML + // + public static Document parseXml(String xml) + throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory + .newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); + return doc; + } + + // + // urlEncode() -- URL encode the string + // + public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + %> diff --git a/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api_conf.jsp b/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api_conf.jsp new file mode 100644 index 0000000000..4612130975 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/api-demo/bbb_api_conf.jsp @@ -0,0 +1,14 @@ + +<%! +// This is the security salt that must match the value set in the BigBlueButton server +//String salt = "4951c2aea43e5af6d9598610b9e0b6c7"; +//String salt = "5e5ff0968546b8aaacce0462a99bca30"; +String salt = "5e5ff0968546b8aaacce0462a99bca30"; +// This is the URL for the BigBlueButton server 4951c2aea43e5af6d9598610b9e0b6c7 +String BigBlueButtonURL = "http://192.168.0.217/bigbluebutton/"; +%> + + + + + diff --git a/bbb-api-demo/target/bbb-api-demo/api-demo/demo5.jsp b/bbb-api-demo/target/bbb-api-demo/api-demo/demo5.jsp new file mode 100644 index 0000000000..bdb12b802b --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/api-demo/demo5.jsp @@ -0,0 +1,120 @@ + + Preupload Presentation

+ + <%@ include file="bbb_api.jsp"%> + <%@ include file="demo_header.jsp"%> + +

Demo #5: Upload a presentation before joining a Course...

+
+ + + + + + + + + + + + + + + + + + + + + + + +
 Full Name:  +
 Upload File:  +
    +
+ +
+ + + + <%@ page import="java.util.List" %> + <%@ page import="java.util.Iterator" %> + <%@ page import="java.io.File" %> + <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%> + <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%> + <%@ page import="org.apache.commons.fileupload.*"%> + <%@ page contentType="text/html;charset=UTF-8" language="java" %> + <%@page import="sun.security.provider.SHA"%> + <%@page import="org.apache.commons.codec.binary.Base64"%> + <%@page import="java.security.MessageDigest"%> + <% + String uname=""; + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + + if (!isMultipart) { + } + else { + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List items = null; + try { + items = upload.parseRequest(request); + } catch (FileUploadException e) { + e.printStackTrace(); + } + out.print(items.size()); + Iterator itr = items.iterator(); + while (itr.hasNext()) { + FileItem item = (FileItem) itr.next(); + String xml = null; + xml = " "; + if (item.isFormField()) + { + String name = item.getFieldName(); + String value = item.getString(); + if(name.equals("username")) + { + uname=value; + } + } else { + try { + + String itemName = item.getName(); + + if(itemName==""){ + xml = " "; + } + else { + byte[] b = item.get(); + String encoded = Base64.encodeBase64String(b); + xml = " "+encoded+"\" "; + } + } catch (Exception e) { + e.printStackTrace(); + } + + String joinURL = getJoinURLXML(uname, "Demo Meeting", "Presentation should be uploaded. It was uploaded as an encoded file", xml ); + if (joinURL.startsWith("http://")) { + %> +

Your presentation has been Uploaded

+ + + <% + } else { + %> + + Error: getJoinURL() failed +

+ <%=joinURL %> + <% + } + } + } + } + %> diff --git a/bbb-api-demo/target/bbb-api-demo/api-demo/demo6.jsp b/bbb-api-demo/target/bbb-api-demo/api-demo/demo6.jsp new file mode 100644 index 0000000000..1d820cb697 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/api-demo/demo6.jsp @@ -0,0 +1,112 @@ + + Preupload Presentation

+ + <%@ include file="bbb_api.jsp"%> + <%@ include file="demo_header.jsp"%> +<% +String fileURL = BigBlueButtonURL.replace("/bigbluebutton",":8080/demo"); +String name1="Demo123.pdf"; +String name2="Demo456.pdf"; +String name3="Demo789.pdf"; +%> + +

Demo #6: Send a presentation URL before joining a Course...

+
+ + + + + + + + + + + + + + + + + + + + + + + +
 Full Name:  +
 File Name:  +
    +
+ +
+ + + + <%@ page import="java.util.List" %> + <%@ page import="java.util.Iterator" %> + <%@ page import="java.io.File" %> + <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%> + <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%> + <%@ page import="org.apache.commons.fileupload.*"%> + <%@ page contentType="text/html;charset=UTF-8" language="java" %> + <%@page import="sun.security.provider.SHA"%> + <%@page import="org.apache.commons.codec.binary.Base64"%> + <%@page import="java.security.MessageDigest"%> + <% + + String uname=""; + String fname=""; + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + + if (!isMultipart) { + } + else { + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List items = null; + try { + items = upload.parseRequest(request); + } catch (FileUploadException e) { + e.printStackTrace(); + } + Iterator itr = items.iterator(); + String xml = null; + while (itr.hasNext()) { + FileItem item = (FileItem) itr.next(); + String name = item.getFieldName(); + String value = item.getString(); + if(name.equals("username")) { + uname=value; + } + if(name.equals("filename")) { + fname=value; + } + } + xml = " "; + String joinURL = getJoinURLXML(uname, "Demo Meeting", "Presentation URL should be passed.", xml ); + if (joinURL.startsWith("http://")) { + %> +

Your presentation URL has been passed

+ + + <% + } else { + %> + + Error: getJoinURL() failed +

+ <%=joinURL %> + <% + } +} +%> diff --git a/bbb-api-demo/target/bbb-api-demo/api-demo/demo_header.jsp b/bbb-api-demo/target/bbb-api-demo/api-demo/demo_header.jsp new file mode 100644 index 0000000000..b5f3b61f3d --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/api-demo/demo_header.jsp @@ -0,0 +1 @@ +
Auto Upload File | Auto Upload File URL diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/bbb_api.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/bbb_api.jsp new file mode 100644 index 0000000000..3b318275be --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/bbb_api.jsp @@ -0,0 +1,448 @@ +<% +/* + BigBlueButton - http://www.bigbluebutton.org + + Copyright (c) 2008-2009 by respective authors (see below). All rights reserved. + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, If not, see . + + Author: Fred Dixon +*/ +%> +<%@page import="javax.xml.transform.dom.DOMSource"%> +<%@page import="javax.xml.transform.stream.StreamResult"%> +<%@page import="javax.xml.transform.OutputKeys"%> +<%@page import="javax.xml.transform.TransformerFactory"%> +<%@page import="javax.xml.transform.Transformer"%> +<%@page import="org.w3c.dom.Element"%> +<%@page import="com.sun.org.apache.xerces.internal.dom.ChildNode"%> +<%@page import="org.w3c.dom.Node"%> +<%@page import="org.w3c.dom.NodeList"%> + +<%@ page + import="java.util.*,java.io.*,java.net.*,javax.crypto.*,javax.xml.parsers.*,org.w3c.dom.Document,org.xml.sax.*" + errorPage="error.jsp" %> + +<%@ page import="org.apache.commons.codec.digest.*"%> +<%@ include file="bbb_api_conf.jsp"%> + +<%! + +// +// Create a meeting with specific +// - meetingID +// - welcome message +// - moderator password +// - viewer password +// - voiceBridge +// - logoutURL +// +public String createMeeting(String meetingID, String welcome, String moderatorPassword, String viewerPassword, Integer voiceBridge, String logoutURL) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + String checksum = ""; + + String attendee_password_param = "&attendeePW=ap"; + String moderator_password_param = "&moderatorPW=mp"; + String voice_bridge_param = ""; + String logoutURL_param = ""; + + if ( (welcome != null) && ! welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + if ( (moderatorPassword != null) && ! moderatorPassword.equals("")) { + moderator_password_param = "&moderatorPW=" + urlEncode(moderatorPassword); + } + + if ( (viewerPassword != null) && ! viewerPassword.equals("")) { + attendee_password_param = "&attendeePW=" + urlEncode(viewerPassword); + } + + if ( (voiceBridge != null) && voiceBridge > 0 ) { + voice_bridge_param = "&voiceBridge=" + urlEncode(voiceBridge.toString()); + } else { + // No voice bridge number passed, so we'll generate a random one for this meeting + Random random = new Random(); + Integer n = 70000 + random.nextInt(9999); + voice_bridge_param = "&voiceBridge=" + n; + } + + if ( (logoutURL != null) && ! logoutURL.equals("")) { + logoutURL_param = "&logoutURL=" + urlEncode(logoutURL); + } + + // + // Now create the URL + // + + String create_parameters = "name=" + urlEncode(meetingID) + "&meetingID=" + urlEncode(meetingID) + + welcome_param + attendee_password_param + moderator_password_param + + voice_bridge_param + logoutURL_param; + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + "&checksum=" + checksum("create" + create_parameters + salt) ); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + + return meetingID; + } + + return "Error " + doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); +} + + + +// +// getJoinMeetingURL() -- get join meeting URL for both viewer and moderator +// +public String getJoinMeetingURL(String username, String meetingID, String password) { + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + + "&password=" + urlEncode(password); + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); +} + + + +// +// Create a meeting and return a URL to join it as moderator +// +public String getJoinURL(String username, String meetingID, String record, String welcome, Map metadata) { + String base_url_create = BigBlueButtonURL + "api/create?"; + String base_url_join = BigBlueButtonURL + "api/join?"; + + String welcome_param = ""; + + Random random = new Random(); + Integer voiceBridge = 70000 + random.nextInt(9999); + + if ( (welcome != null) && ! welcome.equals("")) { + welcome_param = "&welcome=" + urlEncode(welcome); + } + + // + // When creating a meeting, the 'name' parameter is the name of the meeting (not to be confused with + // the username). For example, the name could be "Fred's meeting" and the meetingID could be "ID-1234312". + // + // While name and meetinID could be different, we'll keep them the same. Why? Because calling api/create? + // with a previously used meetingID will return same meetingToken (regardless if the meeting is running or not). + // + // This means the first person to call getJoinURL with meetingID="Demo Meeting" will actually create the + // meeting. Subsequent calls will return the same meetingToken and thus subsequent users will join the same + // meeting. + // + // Note: We're hard-coding the password for moderator and attendee (viewer) for purposes of demo. + // + + String create_parameters = "name=" + urlEncode(meetingID) + "&meetingID=" + urlEncode(meetingID) + + welcome_param + "&attendeePW=ap&moderatorPW=mp&voiceBridge=" + + voiceBridge + "&record=" + record; + + if(metadata!=null){ + String metadata_params=""; + for(String metakey : metadata.keySet()){ + metadata_params = metadata_params + "&meta_" + urlEncode(metakey) + "=" + urlEncode(metadata.get(metakey)); + } + create_parameters = create_parameters + metadata_params; + } + + Document doc = null; + + try { + // Attempt to create a meeting using meetingID + String xml = getURL(base_url_create + create_parameters + "&checksum=" + checksum("create" + create_parameters + salt) ); + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + + + // + // Now create a URL to join that meeting + // + + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + "&password=mp"; + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); + + } + return doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); +} + + +// +// getJoinURLViewer() -- Get the URL to join a meeting as viewer +// +public String getJoinURLViewer(String username, String meetingID) { + + String base_url_join = BigBlueButtonURL + "api/join?"; + String join_parameters = "meetingID=" + urlEncode(meetingID) + "&fullName=" + urlEncode(username) + + "&password=ap"; + + return base_url_join + join_parameters + "&checksum=" + checksum("join" + join_parameters + salt); +} + + + +// +// checksum() -- create a hash based on the shared salt (located in bbb_api_conf.jsp) +// +public static String checksum(String s) { + String checksum = ""; + try { + checksum = org.apache.commons.codec.digest.DigestUtils.shaHex(s); + } catch (Exception e) { + e.printStackTrace(); + } + return checksum; +} + +// +// getURL() -- fetch a URL and return its contents as a String +// +public static String getURL(String url) { + StringBuffer response = null; + + try { + URL u = new URL(url); + HttpURLConnection httpConnection = (HttpURLConnection) u + .openConnection(); + + httpConnection.setUseCaches(false); + httpConnection.setDoOutput(true); + httpConnection.setRequestMethod("GET"); + + httpConnection.connect(); + int responseCode = httpConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStream input = httpConnection.getInputStream(); + + // Read server's response. + response = new StringBuffer(); + Reader reader = new InputStreamReader(input, "UTF-8"); + reader = new BufferedReader(reader); + char[] buffer = new char[1024]; + for (int n = 0; n >= 0;) { + n = reader.read(buffer, 0, buffer.length); + if (n > 0) + response.append(buffer, 0, n); + } + + input.close(); + httpConnection.disconnect(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (response != null) { + return response.toString(); + } else { + return ""; + } +} + +// +// getURLisMeetingRunning() -- return a URL that the client can use to poll for whether the given meeting is running +// +public String getURLisMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return base_url + base_main + "&checksum=" + checksum; +} + +// +// isMeetingRunning() -- check the BigBlueButton server to see if the meeting is running (i.e. there is someone in the meeting) +// +public String isMeetingRunning(String meetingID) { + String base_main = "&meetingID=" + urlEncode(meetingID); + String base_url = BigBlueButtonURL + "api/isMeetingRunning?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("isMeetingRunning" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return doc.getElementsByTagName("running").item(0).getTextContent().trim(); + } + + return "false"; + +} + +public String getMeetingInfoURL(String meetingID, String password) { + String meetingParameters = "meetingID=" + urlEncode(meetingID) + "&password=" + password; + return BigBlueButtonURL + "api/getMeetingInfo?" + meetingParameters + "&checksum=" + checksum("getMeetingInfo" + meetingParameters + salt); +} + +public String getMeetingInfo(String meetingID, String password) { + try { + URLConnection hpCon = new URL(getMeetingInfoURL(meetingID, password)).openConnection(); + + InputStreamReader isr = new InputStreamReader(hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + return data; + } catch (Exception e) { + e.printStackTrace(System.out); + return ""; + } +} + +public String getMeetingsURL() { + String meetingParameters = "random=" + new Random().nextInt(9999); + return BigBlueButtonURL + "api/getMeetings?" + meetingParameters + "&checksum=" + checksum("getMeetings" + meetingParameters + salt); +} + +// +// Calls getMeetings to obtain the list of meetings, then calls getMeetingInfo for each meeting +// and concatenates the result. +// +public String getMeetings() { + try { + + // Call the API and get the result + URLConnection hpCon = new URL(getMeetingsURL()).openConnection(); + InputStreamReader isr = new InputStreamReader(hpCon.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String data = br.readLine(); + Document doc = parseXml(data); + + // tags needed for parsing xml documents + final String startTag = ""; + final String endTag = ""; + final String startResponse = ""; + final String endResponse = ""; + + // if the request succeeded, then calculate the checksum of each meeting and insert it into the document + NodeList meetingsList = doc.getElementsByTagName("meeting"); + + String newXMldocument = startTag; + for (int i = 0; i < meetingsList.getLength(); i++) { + Element meeting = (Element) meetingsList.item(i); + String meetingID = meeting.getElementsByTagName("meetingID").item(0).getTextContent(); + String password = meeting.getElementsByTagName("moderatorPW").item(0).getTextContent(); + + data = getMeetingInfo(meetingID, password); + + if (data.indexOf("") != -1) { + int startIndex = data.indexOf(startResponse) + startTag.length(); + int endIndex = data.indexOf(endResponse); + newXMldocument += "" + data.substring(startIndex, endIndex) + ""; + } + } + newXMldocument += endTag; + + return newXMldocument; + } catch (Exception e) { + e.printStackTrace(System.out); + return null; + } +} + +// +public String endMeeting(String meetingID, String moderatorPassword) { + + String base_main = "meetingID=" + urlEncode(meetingID) + "&password=" + urlEncode(moderatorPassword); + String base_url = BigBlueButtonURL + "api/end?"; + String checksum =""; + + try { + checksum = DigestUtils.shaHex("end" + base_main + salt); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String xml = getURL(base_url + base_main + "&checksum=" + checksum); + + Document doc = null; + try { + doc = parseXml(xml); + } catch (Exception e) { + e.printStackTrace(); + } + + if (doc.getElementsByTagName("returncode").item(0).getTextContent() + .trim().equals("SUCCESS")) { + return "true"; + } + + return "Error " + doc.getElementsByTagName("messageKey").item(0).getTextContent().trim() + + ": " + doc.getElementsByTagName("message").item(0).getTextContent().trim(); + +} + +// +// parseXml() -- return a DOM of the XML +// +public static Document parseXml(String xml) throws ParserConfigurationException, IOException, SAXException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); + return doc; +} + +// +// urlEncode() -- URL encode the string +// +public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; +} +%> diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/create.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/create.jsp new file mode 100644 index 0000000000..2e73d40db8 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/create.jsp @@ -0,0 +1,308 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Create Your Own Meeting + + + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +<%@ include file="demo_header.jsp"%> +

Create Your Own Meeting

+ +

+

+ + + + + + + + +
Create your own meeting. +

+

Step 1. Enter your name:
+ + +
+
+ +
+ + + +<% + } else if (request.getParameter("action").equals("create")) { + // + // User has requested to create a meeting + // + + String username = request.getParameter("username1"); + String meetingID = username + "'s meeting"; + String record = request.getParameter("record1"); + + String meeting_ID = ""; + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, meetingID, record, "
Welcome to %%CONFNAME%%.
", null); + + + String inviteURL = BigBlueButtonURL + "demo/create.jsp?action=invite&meetingID=" + URLEncoder.encode(meetingID, "UTF-8"); +%> + +
+

Meeting Created

+
+ + + + + + + + + + +
+
<%=username%>'s meeting has been + created.
+
+

 

+ + Step 2. Invite others using the following link (shown below): +
+ +
+

  +

Step 3. Click the following link to start your meeting: +

 

+
Start Meeting
+

 

+ +
+ + + + + + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String meetingID = request.getParameter("meetingID"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/create.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&meetingID=" + + URLEncoder.encode(meetingID, "UTF-8"); + + if (isMeetingRunning(meetingID).equals("true")) { + // + // The meeting has started -- bring the user into the meeting. + // +%> + +<% + } else { + // + // The meeting has not yet started, so check until we get back the status that the meeting is running + // + String checkMeetingStatus = getURLisMeetingRunning(meetingID); +%> + + + +
+

<%=meetingID%> has not yet started.

+
+ + + + + + + + + +
+ +

Hi <%=username%>,

+

Now waiting for the moderator to start <%=meetingID%>.

+
+

(Your browser will automatically refresh and join the meeting + when it starts.)

+
+ + +<% +} + } else if (request.getParameter("action").equals("invite")) { + // + // We have an invite to an active meeting. Ask the person for their name + // so they can join. + // + String meetingID = request.getParameter("meetingID"); +%> + +
+

Invite

+
+ +
+ + + + + + + + + +
+ +

You have been invited to join
+ <%=meetingID%>. +

Enter your name:
+
+
+ +
+ + + + +<% + } else if (request.getParameter("action").equals("join")) { + // + // We have an invite request to join an existing meeting and the meeting is running + // + // We don't need to pass a meeting descritpion as it's already been set by the first time + // the meeting was created. + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("meetingID")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo1.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo1.jsp new file mode 100644 index 0000000000..1d8f36a620 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo1.jsp @@ -0,0 +1,119 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + Join a Course + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% +if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // + %> +<%@ include file="demo_header.jsp"%> + +

Demo #1: Join a Course

+ + +
+ + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   +   +   +
+ +
+ + +<% +} else if (request.getParameter("action").equals("create")) { + + // + // Got an action=create + // + + // + // Request a URL to join a meeting called "Demo Meeting" + // Pass null for welcome message to use the default message (see defaultWelcomeMessage in bigbluebutton.properties) + // Update: Added record parameter, default: false + // + String joinURL = getJoinURL(request.getParameter("username"), "Demo Meeting", "false", null, null); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

+<%=joinURL %> + +<% + } +} +%> + + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo2.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo2.jsp new file mode 100644 index 0000000000..e1bc45a47a --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo2.jsp @@ -0,0 +1,130 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + +Join a Selected Course + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> + +<%@ include file="demo_header.jsp"%> + +

Demo #2: Join a Selected Course

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   + Course: +   + + +
+   +   +   +
+ +
+ +<% + } else if (request.getParameter("action").equals("create")) { + // + // Got an action=create + // + + String username = request.getParameter("username"); + String meetingID = request.getParameter("meetingID"); + + // String joinURL = getJoinURL(username, meetingID, "Welcome to " + meetingID ); + // Update: added record parameter, default false + String joinURL = getJoinURL(username, meetingID,"false", "
Welcome to course: %%CONFNAME%%.
", null ); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> <% + } + } + %> <%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo3.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo3.jsp new file mode 100644 index 0000000000..457ebbcc96 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo3.jsp @@ -0,0 +1,293 @@ + + <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + Join a Course (Password Required) + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<% + +// +// We're going to define some sample courses (meetings) below. This API exampe shows how you can create a login page for a course. +// The password below are not available to users as they are compiled on the server. +// + +HashMap allMeetings = new HashMap(); +HashMap meeting; + +String welcome = "
Welcome to %%CONFNAME%%!

For help see our tutorial videos.

To join the voice bridge for this meeting:
(1) click the headset icon in the upper-left, or
(2) dial xxx-xxx-xxxx (toll free:1-xxx-xxx-xxxx) and enter conference ID: %%CONFNUM%%.

"; + + +// +// English courses +// + +meeting = new HashMap(); +allMeetings.put( "ENGL-2013: Research Methods in English", meeting ); // The title that will appear in the drop-down menu + meeting.put("welcomeMsg", welcome); // The welcome mesage + meeting.put("moderatorPW", "prof123"); // The password for moderator + meeting.put("viewerPW", "student123"); // The password for viewer + meeting.put("voiceBridge", "72013"); // The extension number for the voice bridge (use if connected to phone system) + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); // The logout URL (use if you want to return to your pages) + +meeting = new HashMap(); +allMeetings.put( "ENGL-2213: Drama Production I", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72213"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "ENGL-2023: Survey of English Literature", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72023"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +// +// Law Courses +// + +meeting = new HashMap(); +allMeetings.put( "LAW-1323: Fundamentals of Advocacy ", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "71232"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "LAW-2273: Business Organizations", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "72273"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "LAW-3113: Corporate Finance", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "theprof"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "71642"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + + +// +// Professor's Virtaul Office Hours +// + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Steve Stoyan", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70001"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Michael Bailetti", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70002"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + +meeting = new HashMap(); +allMeetings.put( "Virtual Office Hours - Tony Weiss", meeting ); + meeting.put("welcomeMsg", welcome); + meeting.put("moderatorPW", "prof123"); + meeting.put("viewerPW", "student123"); + meeting.put("voiceBridge", "70003"); + meeting.put("logoutURL", "/bigbluebutton/demo/demo3.jsp"); + + +meeting = null; + +Iterator meetingIterator = new TreeSet(allMeetings.keySet()).iterator(); + +if (request.getParameterMap().isEmpty()) { + // + // Assume we want to join a course + // + %> +<%@ include file="demo_header.jsp"%> + +

Demo #3: Join a Course (password required)

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+   + Full Name: +   +
+   + Course: +   + + + +
+   + Password: +   +
+   +   +   +
+ +
+ +Passwords: +
    +
  • prof123 - login as professor (moderator privlidges)
  • +
  • student123 - login as student (viewer privlidges)
  • +
+ + +<% + } else if (request.getParameter("action").equals("create")) { + // + // Got an action=create + // + + String username = request.getParameter("username"); + String meetingID = request.getParameter("meetingID"); + String password = request.getParameter("password"); + + meeting = allMeetings.get( meetingID ); + + String welcomeMsg = meeting.get( "welcomeMsg" ); + String logoutURL = meeting.get( "logoutURL" ); + Integer voiceBridge = Integer.parseInt( meeting.get( "voiceBridge" ).trim() ); + + String viewerPW = meeting.get( "viewerPW" ); + String moderatorPW = meeting.get( "moderatorPW" ); + + // + // Check if we have a valid password + // + if ( ! password.equals(viewerPW) && ! password.equals(moderatorPW) ) { +%> + +Invalid Password, please try again. + +<% + return; + } + + // + // Looks good, let's create the meeting + // + String meeting_ID = createMeeting( meetingID, welcomeMsg, moderatorPW, viewerPW, voiceBridge, logoutURL ); + + // + // Check if we have an error. + // + if( meeting_ID.startsWith("Error ")) { +%> + +Error: createMeeting() failed +

<%=meeting_ID%> + + +<% + return; + } + + // + // We've got a valid meeting_ID and passoword -- let's join! + // + + String joinURL = getJoinMeetingURL(username, meeting_ID, password); +%> + + + +<% + } +%> + +<%@ include file="demo_footer.jsp"%> + + + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4.jsp new file mode 100644 index 0000000000..edee4c1e9b --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4.jsp @@ -0,0 +1,106 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + +<%@page import="org.w3c.dom.*"%> + + + + +Activity Monitor + + + + + + + + + + +<%@ include file="bbb_api.jsp"%> + +
+ +<%@ include file="demo_header.jsp"%> + + +<% +if (request.getParameterMap().isEmpty()) { +%> + +

Demo #4: Activity Monitor

+ +

+ +
+ + +<% +} else if (request.getParameter("action").equals("end")) { + + String mp = request.getParameter("moderatorPW"); + String meetingID = request.getParameter("meetingID"); + + String result = endMeeting(meetingID, mp); + + if ( result.equals("true") ){ + +%> + +

Demo #4: Activity Monitor

+ +<%=meetingID%> has been terminated. + +

+ +
+ +<% } else { %> + +

Demo #4: Activity Monitor

+ + +Unable to end meeting: <%=meetingID%> + +<%=result%> + + + + +<% } + }%> + + <%@ include file="demo_footer.jsp"%> + + + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4_helper.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4_helper.jsp new file mode 100644 index 0000000000..6532a44886 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo4_helper.jsp @@ -0,0 +1,6 @@ + +<%= getMeetings() %> +<%@ include file="bbb_api.jsp" %> +<%@ page contentType="text/xml" %> + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo5.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo5.jsp new file mode 100644 index 0000000000..f0251ab2f2 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo5.jsp @@ -0,0 +1,308 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Create Your Own Meeting + + + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +
+ +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +<%@ include file="demo_header.jsp"%> +

Create Your Own Meeting

+ +

+

+ + + + + + + + +
Create your own meeting. +

+

Step 1. Enter your name:
+ + +
+
+ +
+ + + +<% + } else if (request.getParameter("action").equals("create")) { + // + // User has requested to create a meeting + // + + String username = request.getParameter("username1"); + String meetingID = username + "'s meeting"; + String record = request.getParameter("record1"); + + String meeting_ID = ""; + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, meetingID, record, "
Welcome to %%CONFNAME%%.
", null); + + + String inviteURL = BigBlueButtonURL + "demo/demo5.jsp?action=invite&meetingID=" + URLEncoder.encode(meetingID, "UTF-8"); +%> + +
+

Meeting Created

+
+ + + + + + + + + + +
+
<%=username%>'s meeting has been + created.
+
+

 

+ + Step 2. Invite others using the following link (shown below): +
+ +
+

  +

Step 3. Click the following link to start your meeting: +

 

+
Start Meeting
+

 

+ +
+ + + + + + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String meetingID = request.getParameter("meetingID"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/demo5.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&meetingID=" + + URLEncoder.encode(meetingID, "UTF-8"); + + if (isMeetingRunning(meetingID).equals("true")) { + // + // The meeting has started -- bring the user into the meeting. + // +%> + +<% + } else { + // + // The meeting has not yet started, so check until we get back the status that the meeting is running + // + String checkMeetingStatus = getURLisMeetingRunning(meetingID); +%> + + + +
+

<%=meetingID%> has not yet started.

+
+ + + + + + + + + +
+ +

Hi <%=username%>,

+

Now waiting for the moderator to start <%=meetingID%>.

+
+

(Your browser will automatically refresh and join the meeting + when it starts.)

+
+ + +<% +} + } else if (request.getParameter("action").equals("invite")) { + // + // We have an invite to an active meeting. Ask the person for their name + // so they can join. + // + String meetingID = request.getParameter("meetingID"); +%> + +
+

Invite

+
+ +
+ + + + + + + + + +
+ +

You have been invited to join
+ <%=meetingID%>. +

Enter your name:
+
+
+ +
+ + + + +<% + } else if (request.getParameter("action").equals("join")) { + // + // We have an invite request to join an existing meeting and the meeting is running + // + // We don't need to pass a meeting descritpion as it's already been set by the first time + // the meeting was created. + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("meetingID")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo6.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo6.jsp new file mode 100644 index 0000000000..c784673d45 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo6.jsp @@ -0,0 +1,263 @@ + + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> + + + + + + + Recording Meeting Demo + + + + + +<%@ include file="bbb_api.jsp"%> +<%@ page import="java.util.regex.*"%> + +<%@ include file="demo_header.jsp"%> + +<% + if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // +%> +

Demo Recording

+ +
+
+ Meeting Information +
    +
  • + + +
  • +
  • + + +
  • +
+
+
+ Metadata Details +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+ + +
+ +<% + } else if (request.getParameter("action").equals("create")) { + + String confname=request.getParameter("confname"); + String username = request.getParameter("username1"); + + //metadata + Map metadata=new HashMap(); + + metadata.put("title",request.getParameter("meta_title")); + metadata.put("subject",request.getParameter("meta_subject")); + metadata.put("description",request.getParameter("meta_description")); + metadata.put("creator",request.getParameter("meta_creator")); + metadata.put("contributor",request.getParameter("meta_contributor")); + metadata.put("language",request.getParameter("meta_language")); + metadata.put("identifier",request.getParameter("meta_identifier")); + + // + // This is the URL for to join the meeting as moderator + // + String joinURL = getJoinURL(username, confname, "true", null, metadata); + + + String inviteURL = BigBlueButtonURL + "demo/demo6.jsp?action=invite&confname=" + URLEncoder.encode(confname, "UTF-8"); +%> + + +

Session Created

+ + + + +Start Session + + +<% + } else if (request.getParameter("action").equals("enter")) { + // + // The user is now attempting to joing the meeting + // + String confname = request.getParameter("confname"); + String username = request.getParameter("username"); + + String enterURL = BigBlueButtonURL + + "demo/demo6.jsp?action=join&username=" + + URLEncoder.encode(username, "UTF-8") + "&confname=" + + URLEncoder.encode(confname, "UTF-8"); + + if (isMeetingRunning(confname).equals("true")) { + +%> + +<% + } else { + + String checkMeetingStatus = getURLisMeetingRunning(confname); +%> + + + +

<%=confname%> has not yet started.

+ + + + +<% +} + } else if (request.getParameter("action").equals("invite")) { + + String meetingID = request.getParameter("confname"); +%> + +

Invite

+ +
+ + + + + + + + +
+ + +<% + } else if (request.getParameter("action").equals("join")) { + + String joinURL = getJoinURLViewer(request.getParameter("username"), request.getParameter("confname")); + + if (joinURL.startsWith("http://")) { +%> + + + +<% + } else { +%> + +Error: getJoinURL() failed +

<%=joinURL%> + +<% + } + } + %> + +<%@ include file="demo_footer.jsp"%> + + + diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_footer.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_footer.jsp new file mode 100644 index 0000000000..d142598dca --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_footer.jsp @@ -0,0 +1,2 @@ +

+These demos use the BigBlueButton API. The source code for these demos is available here. diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_header.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_header.jsp new file mode 100644 index 0000000000..de34d86426 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/demo_header.jsp @@ -0,0 +1 @@ +
Join a Course | Join a Selected Course | Join a Course (password required) | Activity Monitor | Create Your Own Meeting | Record Meeting diff --git a/bbb-api-demo/target/bbb-api-demo/bbb-web/error.jsp b/bbb-api-demo/target/bbb-api-demo/bbb-web/error.jsp new file mode 100644 index 0000000000..364fcdbf61 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/bbb-web/error.jsp @@ -0,0 +1,128 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + +<%@ page isErrorPage="true" %> +<%@ page language="java" %> +<%@ page import="java.util.*" %> +<%@ page import="java.io.*" %> + +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + Object statusCode = request.getAttribute("javax.servlet.error.status_code"); + Object exceptionType = request.getAttribute("javax.servlet.error.exception_type"); + Object message = request.getAttribute("javax.servlet.error.message"); +%> + + + +Error Page + + + +

Home

+
+

An Error has occured:

+ + + + + + + + + + + + + + + + + + + + + + +
Status Code<%= statusCode %>
Exception Type<%= exceptionType %>
Message<%= message %>
Exception + <% + if( exception != null ) + { + out.print("
");
+		    exception.printStackTrace(new PrintWriter(out));
+		    out.print("
"); + } + %> +
Root Cause + <% + if( (exception != null) && (exception instanceof ServletException) ) + { + Throwable cause = ((ServletException)exception).getRootCause(); + if( cause != null ) + { + out.print("
");
+			cause.printStackTrace(new PrintWriter(out));
+			out.print("
"); + } + } + %> +
+ +
+Header List + + + + + +<% +String name = ""; +String value = ""; + +java.util.Enumeration headers = request.getHeaderNames(); +while(headers.hasMoreElements()) +{ + name = (String) headers.nextElement(); + value = request.getHeader(name); +%> + + + + +<% +} +%> +
NameValue
<%=name%><%=value%>
+ +Attribute List + + +<% +java.util.Enumeration attributes = request.getAttributeNames(); +while(attributes.hasMoreElements()) +{ + name = (String) attributes.nextElement(); + + if (request.getAttribute(name) == null) + { + value = "null"; + } + else + { + value = request.getAttribute(name).toString(); + } +%> + + + + +<% +} +%> +
<%=name%><%=value%>
+ + + \ No newline at end of file diff --git a/bbb-api-demo/target/bbb-api-demo/index.jsp b/bbb-api-demo/target/bbb-api-demo/index.jsp new file mode 100644 index 0000000000..c38169bb95 --- /dev/null +++ b/bbb-api-demo/target/bbb-api-demo/index.jsp @@ -0,0 +1,5 @@ + + +

Hello World!

+ + diff --git a/bbb-api-demo/target/maven-archiver/pom.properties b/bbb-api-demo/target/maven-archiver/pom.properties new file mode 100644 index 0000000000..4d42182f8a --- /dev/null +++ b/bbb-api-demo/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Thu May 26 08:22:04 UTC 2011 +version=0.8-PRE +groupId=bigbluebutton +artifactId=bbb-api-demo diff --git a/bbb-api-demo/target/war/work/webapp-cache.xml b/bbb-api-demo/target/war/work/webapp-cache.xml new file mode 100644 index 0000000000..11ad5aa36c --- /dev/null +++ b/bbb-api-demo/target/war/work/webapp-cache.xml @@ -0,0 +1,43 @@ + + + + currentBuild + + + bbb-web/demo6.jsp + bbb-web/create.jsp + bbb-web/demo_header.jsp + bbb-web/error.jsp + bbb-web/demo1.jsp + bbb-web/demo4_helper.jsp + bbb-web/demo3.jsp + bbb-web/demo4.jsp + bbb-web/demo2.jsp + bbb-web/bbb_api.jsp + bbb-web/demo5.jsp + bbb-web/demo_footer.jsp + index.jsp + WEB-INF/web.xml + api-demo/demo6.jsp + api-demo/demo_header.jsp + api-demo/bbb_api_conf.jsp + api-demo/bbb_api.jsp + api-demo/demo5.jsp + + + + + + + + junit + junit + 3.8.1 + jar + test + + false + + + + \ No newline at end of file