From e9227a82304f32e20444b0ec884fea80bc60bfa1 Mon Sep 17 00:00:00 2001
From: Richard Alam <ritzalam@gmail.com>
Date: Fri, 27 May 2016 21:31:03 +0000
Subject: [PATCH]  Upgrade to latest grails

 Create new project called bbb-web-api using latest grails.
---
 bbb-web-api/.gitignore                        |    3 +
 bbb-web-api/build.gradle                      |  119 +
 bbb-web-api/gradle.properties                 |    2 +
 bbb-web-api/gradle/wrapper/gradle-wrapper.jar |  Bin 0 -> 53636 bytes
 .../gradle/wrapper/gradle-wrapper.properties  |    6 +
 bbb-web-api/gradlew                           |  160 +
 bbb-web-api/gradlew.bat                       |   90 +
 .../assets/images/apple-touch-icon-retina.png |  Bin 0 -> 14986 bytes
 .../assets/images/apple-touch-icon.png        |  Bin 0 -> 5434 bytes
 .../grails-app/assets/images/favicon.ico      |  Bin 0 -> 10134 bytes
 .../images/grails-cupsonly-logo-white.svg     |   26 +
 .../assets/images/skin/database_add.png       |  Bin 0 -> 658 bytes
 .../assets/images/skin/database_delete.png    |  Bin 0 -> 659 bytes
 .../assets/images/skin/database_edit.png      |  Bin 0 -> 767 bytes
 .../assets/images/skin/database_save.png      |  Bin 0 -> 755 bytes
 .../assets/images/skin/database_table.png     |  Bin 0 -> 726 bytes
 .../assets/images/skin/exclamation.png        |  Bin 0 -> 701 bytes
 .../grails-app/assets/images/skin/house.png   |  Bin 0 -> 806 bytes
 .../assets/images/skin/information.png        |  Bin 0 -> 778 bytes
 .../grails-app/assets/images/skin/shadow.jpg  |  Bin 0 -> 300 bytes
 .../assets/images/skin/sorted_asc.gif         |  Bin 0 -> 835 bytes
 .../assets/images/skin/sorted_desc.gif        |  Bin 0 -> 834 bytes
 .../grails-app/assets/images/spinner.gif      |  Bin 0 -> 2037 bytes
 .../assets/javascripts/application.js         |   21 +
 .../assets/javascripts/bootstrap.js           | 2363 ++++++
 .../assets/javascripts/jquery-2.2.0.min.js    |    4 +
 .../assets/stylesheets/application.css        |   15 +
 .../assets/stylesheets/bootstrap.css          | 6760 +++++++++++++++++
 .../grails-app/assets/stylesheets/errors.css  |  109 +
 .../grails-app/assets/stylesheets/grails.css  | 1059 +++
 .../grails-app/assets/stylesheets/main.css    |  574 ++
 .../grails-app/assets/stylesheets/mobile.css  |   82 +
 .../grails-app/conf/application.groovy        |    7 +
 .../grails-app/conf/application.properties    |  220 +
 bbb-web-api/grails-app/conf/application.yml   |  129 +
 bbb-web-api/grails-app/conf/logback.groovy    |   34 +
 .../conf/spring/bbb-redis-messaging.xml       |   72 +
 .../grails-app/conf/spring/bbb-redis-pool.xml |   32 +
 .../grails-app/conf/spring/doc-conversion.xml |  124 +
 .../grails-app/conf/spring/resources.groovy   |    3 +
 .../grails-app/conf/spring/resources.xml      |  104 +
 .../conf/spring/turn-stun-servers.xml         |   68 +
 .../web/controllers/ApiController.groovy      | 2128 ++++++
 .../controllers/PresentationController.groovy |  296 +
 .../web/controllers/UrlMappings.groovy        |   80 +
 .../grails-app/i18n/messages.properties       |   56 +
 .../grails-app/i18n/messages_cs_CZ.properties |   55 +
 .../grails-app/i18n/messages_da.properties    |   56 +
 .../grails-app/i18n/messages_de.properties    |   55 +
 .../grails-app/i18n/messages_es.properties    |   55 +
 .../grails-app/i18n/messages_fr.properties    |   19 +
 .../grails-app/i18n/messages_it.properties    |   55 +
 .../grails-app/i18n/messages_ja.properties    |   55 +
 .../grails-app/i18n/messages_nb.properties    |   56 +
 .../grails-app/i18n/messages_nl.properties    |   55 +
 .../grails-app/i18n/messages_pl.properties    |   59 +
 .../grails-app/i18n/messages_pt_BR.properties |   59 +
 .../grails-app/i18n/messages_pt_PT.properties |   34 +
 .../grails-app/i18n/messages_ru.properties    |   31 +
 .../grails-app/i18n/messages_sv.properties    |   55 +
 .../grails-app/i18n/messages_th.properties    |   55 +
 .../grails-app/i18n/messages_zh_CN.properties |   18 +
 bbb-web-api/grails-app/init/BootStrap.groovy  |    7 +
 .../init/bbb/web/api/Application.groovy       |   10 +
 .../web/services/PresentationService.groovy   |  171 +
 bbb-web-api/grails-app/views/error.gsp        |   31 +
 bbb-web-api/grails-app/views/index.gsp        |   79 +
 bbb-web-api/grails-app/views/layouts/main.gsp |   51 +
 bbb-web-api/grails-app/views/notFound.gsp     |   14 +
 .../api/ClientConfigServiceHelperImp.groovy   |   46 +
 .../api/RecordingServiceHelperImp.groovy      |  132 +
 .../GeneratedSlidesInfoHelperImp.groovy       |   52 +
 .../java/org/bigbluebutton/api/ApiErrors.java |   78 +
 .../api/ClientConfigService.java              |   59 +
 .../api/IClientConfigServiceHelper.java       |   26 +
 .../org/bigbluebutton/api/MeetingService.java |  941 +++
 .../api/ParamsProcessorUtil.java              |  780 ++
 .../bigbluebutton/api/RecordingService.java   |  404 +
 .../api/RecordingServiceHelper.java           |   29 +
 .../main/java/org/bigbluebutton/api/Util.java |   30 +
 .../org/bigbluebutton/api/domain/Config.java  |   14 +
 .../org/bigbluebutton/api/domain/Meeting.java |  494 ++
 .../bigbluebutton/api/domain/Playback.java    |   60 +
 .../org/bigbluebutton/api/domain/Poll.java    |   81 +
 .../bigbluebutton/api/domain/Recording.java   |  209 +
 .../bigbluebutton/api/domain/Recordings.java  |   44 +
 .../org/bigbluebutton/api/domain/User.java    |  138 +
 .../bigbluebutton/api/domain/UserSession.java |   53 +
 .../api/messaging/Constants.java              |   96 +
 .../api/messaging/MeetingMessageHandler.java  |  194 +
 .../api/messaging/MessageBuilder.java         |   36 +
 .../api/messaging/MessageDistributor.java     |   25 +
 .../api/messaging/MessageHandler.java         |   23 +
 .../api/messaging/MessageListener.java        |   26 +
 .../api/messaging/MessageReceiver.java        |  114 +
 .../api/messaging/MessageSender.java          |  106 +
 .../api/messaging/MessageToJson.java          |   72 +
 .../api/messaging/MessageToSend.java          |   19 +
 .../api/messaging/MessagingConstants.java     |   60 +
 .../api/messaging/MessagingService.java       |   45 +
 .../api/messaging/ReceivedMessage.java        |   25 +
 .../api/messaging/ReceivedMessageHandler.java |   74 +
 .../api/messaging/RedisMessagingService.java  |  173 +
 .../api/messaging/RedisStorageService.java    |  126 +
 .../converters/MessageFromJsonConverter.java  |   19 +
 .../messages/CreateMeetingMessage.java        |   39 +
 .../messages/DestroyMeetingMessage.java       |   12 +
 .../messages/EndMeetingMessage.java           |   12 +
 .../converters/messages/KeepAliveMessage.java |   12 +
 .../messages/RegisterUserMessage.java         |   24 +
 .../messages/CreateBreakoutRoom.java          |   27 +
 .../api/messaging/messages/CreateMeeting.java |   12 +
 .../messaging/messages/EndBreakoutRoom.java   |    9 +
 .../api/messaging/messages/EndMeeting.java    |   10 +
 .../api/messaging/messages/IMessage.java      |    5 +
 .../messaging/messages/KeepAliveReply.java    |   12 +
 .../messaging/messages/MeetingDestroyed.java  |    9 +
 .../api/messaging/messages/MeetingEnded.java  |    9 +
 .../messaging/messages/MeetingStarted.java    |    9 +
 .../api/messaging/messages/RegisterUser.java  |   22 +
 .../messages/RemoveExpiredMeetings.java       |    5 +
 .../messages/StunTurnInfoRequested.java       |   11 +
 .../api/messaging/messages/UserJoined.java    |   19 +
 .../messaging/messages/UserJoinedVoice.java   |   11 +
 .../api/messaging/messages/UserLeft.java      |   11 +
 .../api/messaging/messages/UserLeftVoice.java |   11 +
 .../messaging/messages/UserListeningOnly.java |   13 +
 .../messaging/messages/UserSharedWebcam.java  |   13 +
 .../messaging/messages/UserStatusChanged.java |   15 +
 .../messages/UserUnsharedWebcam.java          |   13 +
 .../api/responses/InvalidResponse.java        |   13 +
 .../messages/BbbAppsIsAliveMessage.java       |   53 +
 .../common/messages/MessageBuilder.java       |   41 +
 .../ConversionMessageConstants.java           |   39 +
 .../presentation/ConversionUpdateMessage.java |  111 +
 .../DocumentConversionService.java            |   24 +
 .../DocumentConversionServiceImp.java         |   82 +
 .../presentation/FileTypeConstants.java       |   45 +
 .../GeneratedSlidesInfoHelper.java            |   24 +
 .../presentation/ImageToSwfSlide.java         |   94 +
 .../OfficeToPdfConversionSuccessFilter.java   |   78 +
 .../org/bigbluebutton/presentation/Page.java  |   16 +
 .../presentation/PageAnalyser.java            |   32 +
 .../presentation/PageConverter.java           |   26 +
 .../presentation/PageCounter.java             |   26 +
 .../presentation/PageExtractor.java           |   26 +
 .../presentation/PdfToSwfSlide.java           |  148 +
 .../PresentationUrlDownloadService.java       |  169 +
 .../presentation/SupportedDocumentFilter.java |   73 +
 .../presentation/SupportedFileTypes.java      |   80 +
 .../presentation/SvgImageCreator.java         |   27 +
 .../presentation/TextFileCreator.java         |   24 +
 .../presentation/ThumbnailCreator.java        |   24 +
 .../presentation/UploadedPresentation.java    |   92 +
 .../org/bigbluebutton/presentation/Util.java  |   44 +
 .../AbstractPageConverterHandler.java         |   92 +
 .../handlers/Pdf2PngPageConverterHandler.java |   30 +
 .../handlers/Pdf2SwfPageConverterHandler.java |  109 +
 .../handlers/Png2SwfPageConverterHandler.java |   30 +
 .../imp/CountingPageException.java            |   49 +
 .../imp/ExternalProcessExecutor.java          |   77 +
 .../imp/GhostscriptPageExtractor.java         |   56 +
 .../imp/ImageMagickPageConverter.java         |   54 +
 .../ImageToSwfSlidesGenerationService.java    |  194 +
 .../imp/Jpeg2SwfPageConverter.java            |   53 +
 .../imp/Office2PdfPageConverter.java          |   71 +
 .../imp/OfficeToPdfConversionService.java     |   70 +
 .../presentation/imp/PageCounterService.java  |   85 +
 .../imp/Pdf2SwfPageConverter.java             |  263 +
 .../presentation/imp/Pdf2SwfPageCounter.java  |  106 +
 .../imp/PdfPageToImageConversionService.java  |   67 +
 .../imp/PdfToSwfSlidesGenerationService.java  |  302 +
 .../imp/Png2SwfPageConverter.java             |   51 +
 .../presentation/imp/SvgImageCreatorImp.java  |  105 +
 .../SwfSlidesGenerationProgressNotifier.java  |  104 +
 .../presentation/imp/TextFileCreatorImp.java  |  121 +
 .../presentation/imp/ThumbnailCreatorImp.java |  163 +
 .../ExpiredMeetingCleanupTimerTask.java       |   55 +
 .../web/services/IStorageService.java         |   30 +
 .../web/services/KeepAliveMessage.java        |    5 +
 .../web/services/KeepAlivePing.java           |    7 +
 .../web/services/KeepAlivePong.java           |   12 +
 .../web/services/KeepAliveService.java        |  169 +
 .../web/services/RedisStorageService.java     |   90 +
 .../RegisteredUserCleanupTimerTask.java       |   55 +
 .../web/services/turn/StunServer.java         |   10 +
 .../web/services/turn/StunTurnService.java    |   42 +
 .../web/services/turn/TurnEntry.java          |   33 +
 .../web/services/turn/TurnServer.java         |   77 +
 .../freemarker/get-recordings-empty.ftl       |   11 +
 .../WEB-INF/freemarker/get-recordings.ftl     |   41 +
 .../WEB-INF/freemarker/invalid-response.ftl   |    8 +
 .../respond-with-conference-details.ftl       |   55 +
 .../freemarker/respond-with-conference.ftl    |   19 +
 194 files changed, 25511 insertions(+)
 create mode 100644 bbb-web-api/.gitignore
 create mode 100755 bbb-web-api/build.gradle
 create mode 100755 bbb-web-api/gradle.properties
 create mode 100755 bbb-web-api/gradle/wrapper/gradle-wrapper.jar
 create mode 100755 bbb-web-api/gradle/wrapper/gradle-wrapper.properties
 create mode 100755 bbb-web-api/gradlew
 create mode 100755 bbb-web-api/gradlew.bat
 create mode 100755 bbb-web-api/grails-app/assets/images/apple-touch-icon-retina.png
 create mode 100755 bbb-web-api/grails-app/assets/images/apple-touch-icon.png
 create mode 100755 bbb-web-api/grails-app/assets/images/favicon.ico
 create mode 100755 bbb-web-api/grails-app/assets/images/grails-cupsonly-logo-white.svg
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/database_add.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/database_delete.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/database_edit.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/database_save.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/database_table.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/exclamation.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/house.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/information.png
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/shadow.jpg
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/sorted_asc.gif
 create mode 100755 bbb-web-api/grails-app/assets/images/skin/sorted_desc.gif
 create mode 100755 bbb-web-api/grails-app/assets/images/spinner.gif
 create mode 100755 bbb-web-api/grails-app/assets/javascripts/application.js
 create mode 100755 bbb-web-api/grails-app/assets/javascripts/bootstrap.js
 create mode 100755 bbb-web-api/grails-app/assets/javascripts/jquery-2.2.0.min.js
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/application.css
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/bootstrap.css
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/errors.css
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/grails.css
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/main.css
 create mode 100755 bbb-web-api/grails-app/assets/stylesheets/mobile.css
 create mode 100755 bbb-web-api/grails-app/conf/application.groovy
 create mode 100755 bbb-web-api/grails-app/conf/application.properties
 create mode 100755 bbb-web-api/grails-app/conf/application.yml
 create mode 100755 bbb-web-api/grails-app/conf/logback.groovy
 create mode 100755 bbb-web-api/grails-app/conf/spring/bbb-redis-messaging.xml
 create mode 100755 bbb-web-api/grails-app/conf/spring/bbb-redis-pool.xml
 create mode 100755 bbb-web-api/grails-app/conf/spring/doc-conversion.xml
 create mode 100755 bbb-web-api/grails-app/conf/spring/resources.groovy
 create mode 100755 bbb-web-api/grails-app/conf/spring/resources.xml
 create mode 100755 bbb-web-api/grails-app/conf/spring/turn-stun-servers.xml
 create mode 100755 bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
 create mode 100755 bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy
 create mode 100755 bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/UrlMappings.groovy
 create mode 100755 bbb-web-api/grails-app/i18n/messages.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_cs_CZ.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_da.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_de.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_es.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_fr.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_it.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_ja.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_nb.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_nl.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_pl.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_pt_BR.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_pt_PT.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_ru.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_sv.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_th.properties
 create mode 100755 bbb-web-api/grails-app/i18n/messages_zh_CN.properties
 create mode 100755 bbb-web-api/grails-app/init/BootStrap.groovy
 create mode 100755 bbb-web-api/grails-app/init/bbb/web/api/Application.groovy
 create mode 100755 bbb-web-api/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy
 create mode 100755 bbb-web-api/grails-app/views/error.gsp
 create mode 100755 bbb-web-api/grails-app/views/index.gsp
 create mode 100755 bbb-web-api/grails-app/views/layouts/main.gsp
 create mode 100755 bbb-web-api/grails-app/views/notFound.gsp
 create mode 100755 bbb-web-api/src/main/groovy/org/bigbluebutton/api/ClientConfigServiceHelperImp.groovy
 create mode 100755 bbb-web-api/src/main/groovy/org/bigbluebutton/api/RecordingServiceHelperImp.groovy
 create mode 100755 bbb-web-api/src/main/groovy/org/bigbluebutton/presentation/GeneratedSlidesInfoHelperImp.groovy
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/ApiErrors.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/ClientConfigService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/IClientConfigServiceHelper.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/MeetingService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingServiceHelper.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/Util.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Config.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Meeting.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Playback.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Poll.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recording.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recordings.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/User.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/domain/UserSession.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/Constants.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageBuilder.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageListener.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageReceiver.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageSender.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToJson.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToSend.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingConstants.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisMessagingService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/MessageFromJsonConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/CreateMeetingMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DestroyMeetingMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/EndMeetingMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/KeepAliveMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/RegisterUserMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateBreakoutRoom.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateMeeting.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndBreakoutRoom.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndMeeting.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/IMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/KeepAliveReply.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingDestroyed.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingEnded.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingStarted.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RegisterUser.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RemoveExpiredMeetings.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/StunTurnInfoRequested.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoined.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoinedVoice.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeft.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeftVoice.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserListeningOnly.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserSharedWebcam.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserStatusChanged.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserUnsharedWebcam.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/api/responses/InvalidResponse.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/common/messages/BbbAppsIsAliveMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/common/messages/MessageBuilder.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionUpdateMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/FileTypeConstants.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/GeneratedSlidesInfoHelper.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/ImageToSwfSlide.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/Page.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageAnalyser.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageCounter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageExtractor.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedDocumentFilter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/Util.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/AbstractPageConverterHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2PngPageConverterHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2SwfPageConverterHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Png2SwfPageConverterHandler.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/CountingPageException.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/GhostscriptPageExtractor.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageMagickPageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Office2PdfPageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PageCounterService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageCounter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfPageToImageConversionService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/ExpiredMeetingCleanupTimerTask.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/IStorageService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveMessage.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePing.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePong.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunServer.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunTurnService.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnEntry.java
 create mode 100755 bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnServer.java
 create mode 100755 bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings-empty.ftl
 create mode 100755 bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings.ftl
 create mode 100755 bbb-web-api/src/main/webapp/WEB-INF/freemarker/invalid-response.ftl
 create mode 100755 bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference-details.ftl
 create mode 100755 bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference.ftl

diff --git a/bbb-web-api/.gitignore b/bbb-web-api/.gitignore
new file mode 100644
index 0000000000..8ccdef83f8
--- /dev/null
+++ b/bbb-web-api/.gitignore
@@ -0,0 +1,3 @@
+lib/
+build/
+.idea/
diff --git a/bbb-web-api/build.gradle b/bbb-web-api/build.gradle
new file mode 100755
index 0000000000..2feb2082f7
--- /dev/null
+++ b/bbb-web-api/build.gradle
@@ -0,0 +1,119 @@
+buildscript {
+    ext {
+        grailsVersion = project.grailsVersion
+    }
+    repositories {
+        mavenLocal()
+        maven { url "https://repo.grails.org/grails/core" }
+    }
+    dependencies {
+        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
+        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.8.2"
+        classpath "org.grails.plugins:hibernate4:5.0.6"
+    }
+}
+
+version "0.1"
+group "bigbluebutton"
+
+apply plugin:"eclipse"
+apply plugin:"idea"
+apply plugin:"war"
+apply plugin:"org.grails.grails-web"
+apply plugin:"org.grails.grails-gsp"
+apply plugin:"asset-pipeline"
+
+ext {
+    grailsVersion = project.grailsVersion
+    gradleWrapperVersion = project.gradleWrapperVersion
+}
+
+repositories {
+    jcenter()
+    mavenCentral()
+    mavenLocal()
+    maven {
+        url "https://repo.grails.org/grails/core"
+
+        // Look for artifacts here if not found at the above location
+        artifactUrls "http://oss.sonatype.org/content/repositories/snapshots"
+
+        artifactUrls "http://oss.sonatype.org/content/repositories/releases"
+    }
+}
+
+dependencyManagement {
+    imports {
+        mavenBom "org.grails:grails-bom:$grailsVersion"
+    }
+    applyMavenExclusions false
+}
+
+dependencies {
+    compile "org.springframework.boot:spring-boot-starter-logging"
+    compile "org.springframework.boot:spring-boot-autoconfigure"
+    compile "org.grails:grails-core"
+    compile "org.springframework.boot:spring-boot-starter-actuator"
+    compile "org.springframework.boot:spring-boot-starter-tomcat"
+    compile "org.grails:grails-dependencies"
+    compile "org.grails:grails-web-boot"
+    compile "org.grails.plugins:cache"
+    compile "org.grails.plugins:scaffolding"
+    compile "org.grails.plugins:hibernate4"
+    compile "org.hibernate:hibernate-ehcache"
+    console "org.grails:grails-console"
+    profile "org.grails.profiles:web:3.1.7"
+    runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.8.2"
+    runtime "com.h2database:h2"
+    testCompile "org.grails:grails-plugin-testing"
+    testCompile "org.grails.plugins:geb"
+    testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
+    testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
+
+    //redis
+    compile 'redis.clients:jedis:2.7.2'
+    compile 'org.apache.commons:commons-pool2:2.3'
+
+    compile 'commons-lang:commons-lang:2.5'
+    compile 'commons-io:commons-io:2.4'
+    compile 'com.google.code.gson:gson:2.5'
+    compile 'commons-httpclient:commons-httpclient:3.1'
+
+    compile 'org.bigbluebutton:bbb-common-message:0.0.18-SNAPSHOT'
+    compile 'com.zaxxer:nuprocess:1.0.4'
+
+    compile 'org.json:json:20160212'
+
+    // XML creation speedup
+    compile 'org.freemarker:freemarker:2.3.23'
+
+    // http://mvnrepository.com/artifact/com.artofsolving/jodconverter
+    compile group: 'com.artofsolving', name: 'jodconverter', version: '2.2.1'
+    // http://mvnrepository.com/artifact/org.libreoffice/unoil
+    compile group: 'org.libreoffice', name: 'unoil', version: '5.1.2'
+    // http://mvnrepository.com/artifact/org.libreoffice/unoloader
+    compile group: 'org.libreoffice', name: 'unoloader', version: '5.1.2'
+    // http://mvnrepository.com/artifact/org.libreoffice/officebean
+    compile group: 'org.libreoffice', name: 'officebean', version: '5.1.2'
+    // http://mvnrepository.com/artifact/org.libreoffice/juh
+    compile group: 'org.libreoffice', name: 'juh', version: '5.1.2'
+    // http://mvnrepository.com/artifact/org.libreoffice/jurt
+    compile group: 'org.libreoffice', name: 'jurt', version: '5.1.2'
+    // http://mvnrepository.com/artifact/org.libreoffice/ridl
+    compile group: 'org.libreoffice', name: 'ridl', version: '5.1.2'
+}
+
+task resolveDeps(type: Copy) {
+    into('lib')
+    from configurations.default
+    from configurations.default.allArtifacts.file
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = gradleWrapperVersion
+}
+
+assets {
+    minifyJs = true
+    minifyCss = true
+}
diff --git a/bbb-web-api/gradle.properties b/bbb-web-api/gradle.properties
new file mode 100755
index 0000000000..0fe7504d6f
--- /dev/null
+++ b/bbb-web-api/gradle.properties
@@ -0,0 +1,2 @@
+grailsVersion=3.1.7
+gradleWrapperVersion=2.13
diff --git a/bbb-web-api/gradle/wrapper/gradle-wrapper.jar b/bbb-web-api/gradle/wrapper/gradle-wrapper.jar
new file mode 100755
index 0000000000000000000000000000000000000000..941144813d241db74e1bf25b6804c679fbe7f0a3
GIT binary patch
literal 53636
zcmafaW0a=B^559DjdyI@wr$%scWm3Xy<^+Pj_sKpY&N+!|K#4>Bz;ajPk*RBjZ;<q
zE3~2vC>RV75EK-Uv!Ig%(BB5~-#>pF^k0$_Qx&3<k{4$H1ycMM#-UqrY=OTFs(%UX
ze`k~vl9v(}Q&FXt6MvMOo|2WJqhElRp`)Iio@-WSTxQunewd~kmzt5Ap^>5mhPeng
zP5V`%P1S)(UiPcRczm!G=UnT-`Q91$f1A+!-&O|pcR~kei+@?vzw^NUlgGl@$kf*C
z|H+E_udE)<MvjhV&i}^{%V1OU;=imbe~I)z4-s|nu(x$EG9_c8H?cKxaq)|hgBfH*
z3fr?})24A}2!>q?&-+Q}NKDTwWGi9|EhSaen+=P&UpS2Bbjf?dM=<SM|9S*XBr-UF
zWi0I5#*sedS4nQv)mpotj&>=%4Q|xN(%II>dI89;ro*BL4Red4p@gCHx)jxu84C!g
zjsX&OW)$y=#n_cmkmSKx8wB`wsWLl2JqjeaVk7bSmJ^1~lfVg!V?hu`#16r`(c%03
z+bNIihOMIg6#&P-M=bjP*`tb=i>sNPqO-%_!*aDUbNSoz^b&G&wKTJLwK6esb#VU2
zA(X1vIiLt3`C|Yg#ug4M4Qo?3SG`q_qZ}3taiC*=Kr_iz$;k@X8G%~Vd6+sRKGZ)&
z+p*q5z7@wb3#JkQquvh9UhzIo^YV1R9-Xe;0!?~alf(u?!-9j_P;Ij}#>Jwst7xv?
z;G^nv*pMKM4YURMz)fK4?^o)Dcc}21N-htU8ERJf1bHs;abY~r3A|7luMI)GB6dDK
z`J>5Jv|%#U5I&KT%fFbdBP)B6kleNyTvxS0rL65!r@*aV5+OC6JOWULy|fU`rtGA4
zpTf41dqh+{7_Pwm$Fs8^Vb!tHbcC-}I`skBCK;FzaJce~-$4Pt?1@r%_$rO}`9UT7
zSX5*>iy%>Xc8mbiQl^ZEgLSr%8hHc?Cm_^TR2a;fB{(joOtfvO7b)Do$8Sl9;dvVr
zgJnGKAUpQ0O~(W`21R%m@d<D}2OB;DhRnvl)VY9bW^;f<+y$S!=A9$_uoi#kh41`^
z(~<Y16_aAHREr9Tz{-=8`joJCXBH<>)wFTZN=-_R3{~N+V)|9y!dZ2Gsh{a2TeDzb
zE)?K2{8YP0s$G;TlctY`(Kd(lAuA83rJWo?G-jq<YXoAeOPVc+@Q&OmtrUCQPyD?r
zpJVb<esh#d*}(A}?xg&OvvG~Czh>M3oPEqBA0;lXmC;h`uW)Emx=o#*Gr)Fk2?4Mg
z6Pv$Em4?wXI^;1nmKpw+G5PO$dwQkmQuSBbw*C^yf0jC_|EXI4kSVd)pMMn#F8t5*
z`3V|w4~+h^@qJG<45*OelYTohyEM;*D}Od5;XnimPbxOlMEd9ZqwfwO5XPC$nKu-a
ze-RBin*vnwImM~QYzkn*2s6xJl2yk-IkcISSaZi%DJ4_g0+DaZ$B(J8;x$yL<x^|s
zQe;pwJEb(7Qn_LBt_Ufd4QKGqgrQS<<0U1!gDmE|_(UZ_&`}YxGZaI2%1*}Zdi}}v
zb|3)sUv~;t>Aj=-SHG10>KEOA-l@d@Fj#6XX3mlhc4o2;4mNI%|JZb_ijD$~5ZbqR
zqTcGWat)xh%~}UcXG8m1ZE1L_>W3;65wwD77<3(dx2cxxr$#TCwe{i{|C0n8-;grR
zcu4m|=Zr_6%gOZgt$=_(h~{8bu+sE|XVE@Yo>U|il%c-3?%NL}@dl!U&fo-~UL<cN
zab-)nK^bTnxMZE*FTaJT4K^q097>-Sh2-bb+?VoQ!yPZyIoVjJ8mhHtUF5pECK-2P
zY0R3=WAbJ&WqiC7jVzZMar2CPz=y1z5BtN`USauJJIpuBUK0xi@&Jrr?71-HF(tCu
zw;VPR+cUTk7?^&XW<%6ibyT13jQjYR@ZqA9PSx5gY}6QQ{N3WcvwC*r#{{e$-IvRr
zlTPwkZq|Mso5&Vev6P>5S#fQ4+Bu95+8fp$rN45@bWV(eh&Q8IsFKt~8HIHDy_%#V
ze<2Hz^(Z&SphG;H!vhD%-Q6@+c!r>(zap7uoaKFpFSSr_n?dO<ReMHzEZ3(!!B%_5
zBf>p;;6b|G^-KP~%Si8yQ@p7;xW^eXO!dKDBgVOnA;#$UBS-1ApYUWL%5_RO>+q8f
zx16bCq}~0|#0TUgn0FL`bu;F(JW9LsTge;$D>BL|34H|1YA|_6A^`1(<CBOulT#!q
z8Erw5Wf2q>)6hUC0We`m!x;xjrbZY@#Y=`i$V$+fte|cB#5&}ce#UU~73>`*m{<YP
z7aEeDz%4TP2BDR)7DgfpN1`6-4i_&qOBCLDIf#M*pm-r)y6CS<r_%9N(SSl{C<ukD
z{N`G|L;B$Y7Z5@FGH8EAF6>;U=Kk_;3W;~9w>1I|1oZnaGGO`7Vk+ioV(aE&<!&Z!
zWoss6Vq)gvB4=c8WMTGCtDmYa<A`d8_s1@a!m7)TSdduJ7AUMmF&Kcc6r8qhY^hxU
zUkZ-iKEbZnwG*2yC*w!NfAQgVca~esOiHH-If^YIF&4so6m&m0g3XYJ#XvB;*Pfl$
zhmY`1@aOdQ&u?~t?~fIDAjG3&qPD_j5ag2K#bXH%Bz?TsBT*!N*QhBerwix;jVmi5
zF7mWKBKhLI_#mKJFeTDRHcs4O7Eg9bl{s)Hgt?DGFh&3x3$STe7LF)gICbKXBNEQ6
zhA~(dVX;4!e+J5GNgO5joEw?S^{Kaln=9D|d8qPfDie7bjX3?PjZRjgO`>8dV{C9O
zmV15?rW!PQ8+%ojSa&s%khFBgY<5>3tL+MoimT95t97_JVVWX=90l%gGEY?Vv?w;J
z8O84C;*hFTbqF`LHx`zt-Ez&Wj`T=<AI;)a&B+$$#+*`9H#T;As`no2kxd5ECu1R8
z6&_YSyD8IU;X)avlpJg13I!)9l+7Z0yew=~XS$nOOB-)m6P(N<v&wLM89*03ZyW1$
z^zXNQFE*2<jC^7p_v{(0c;sYzb8)ie&LjwUi*WQI#pmFoAfuQ_*~-TQCKgzu$oUkI
zndLu-M9{zob8Jnc$}u%u7Z8_8>~kB}TEnOVGUF%Pv_jdA3@NpG8Gn9!+QJj);v3m;
z?>J}t7FrdV*}}mM^;@Vuh8v;RUcR-K8%sBTzVlldaa$Zv8{AYfGgg#4GZ*61T2|G`
zCwlW)#S7PwY0Hl1lnpW-;)QaNw5laxp<yuEVHU^Itk-H`E@fV=-*)o}Yl;CpFcS>Q
zV|O>G1oH|=V>1jSH8|ay;!|0BtGAk>8BPI=W3C%D=3>UNFhc?K;~4|d{yk(zW<4ZE
zOVVQL`;DV!y2I7}x=Hsq`ss-SD*iphM{=@F1~>0FR5-@Ir%l9#%-3-)!+23pcn(fa
zBxzNq;VZVLx(l|(v2dB{rgfd9H#uUqEX<;>PF20v!v16N9%eleuU~J1qY>jD_lYs_
zi57Y3RAHfIA6ZTaLx*`uiWul@^^=t^&|*&tR@O!E(GhbBiS}kG)6Wax#{}H@cMhgM
zsJl{nRf|;xnQGh4lgO?#+eR?4Q1H3AlU8biLBFSiE4(RT+PEjf8RS9$^66!lSv1q-
zfN@5YX3{=8_9V4%-^(hH>1aE-lAP1)AoSW)f(|dirJ*b2ld7JAYU<0&SOV0<6|v-M
zv#Rj@EeO~${gxHfD86ZIJ^D4j<_ZmO+_QMZ^uCT1m-^R})FH!xw5n?9An{fDOh1TU
zya=C~5^tcBNTcpoKzpLQyig=$6uGAfSnd+S#+Mw9cE9Wbna#FsaLS3<>^or;Om@^#
z^qf*Wc&zp7wmR%3z~MEP?g*4g>Tt3eFdgLwV}Ip@k|NGAT@|D4cwW2}rUOr~fZh(=
zP^HWba4^CP#0OESh6d}FDRRMgcK_I>Qq4^})Th$-hhLfDry_uY?2~|GXzd$iILK7x
z|AL!gslc{`sm&bS?BKY{6$a=NlwEL3{JxnpqOM2u=~OJWeZXPY?c*W6Vx1{)F90KI
zNz4nIpt6Mt^P(u4X*O)z-gd!vLpek@D%!rlBBL0iIM{JPs(T|L(AB5#WYOnRXn3Gt
zdFLu~iq7l`+spMM^dH1O{cdkg=gRDl^sej9cm=qu56E&TH$g*Y+=uX%zH!tNe!M$e
zAj2hc2ahF4u<Q+obcCwQ9wPCzE@*%l6yxs|fbJav{0Ekv)_xdR9$<L2Pke;*2uyrL
z_wfAs63MG@Mg(c`4as|H4u)hBF^EqZm7Z14D+hg|<tjIy^o)FCC!nm2VeTi6!x-&B
z>_=H5PB~&s{l)c83HU=srLTPPL;Yz7xs9$LsuY87YUils%%j4(=kJB08_wYtX379w
zU2)Q8O&1GFDRoWW8=u3v)w%~=lE%EUy@g$|RU&~+%|vwG!TUn^ui#}GUSB-%FL-%}
z_`iY|jeqz~A`bTERu*o~My^&4_WuMg$#x2;LP%qOwoX?=_=5wBib$@Ba|-rZpb^!W
z)Zox1eMRaV(@2lww)NQVRjf#u?!yQN5Y2LWbqZ>>hB;W8SswGhu5~{?=H?85PVN8^
zG8q$w?9q5Ja5qi@V>7%Qubo~4Gr~C0R=lS3FLnZVSLz%MdJ#qqPL}@6@MADBwKLD<
zaACW@qt12UN-N4uxb2Fi*vjc%ds#w2!wYv+9|v*_G;Q7Eu@()kjx15)i*}b;wi-jo
z!#!KuW)d{rUMuq)*5jVre3qMfUd^jfcdu_UbM2Oz-?hk4e+FH%EaTLzv2W&e?ls2D
z<$3wqdX38e($G6C-nsFnupr*{-GW)A@99yjop6}@a8_ybZj5M7D^*%pqAow8udBSO
z&Wfn|^HL=)(Vb)=x`ABTZgD{Bzo#6hN+>TNF?-7=nrhim5h=2C?d`J)n|<rA+Pbgx
zjRTJkx1euePhZb=(XBJ{`{brNmctD`WSj*q$DjC{=AI2LVNLO{Y&td^7+!3b^Euxj
zwT|21Q2cU!6P6M9I|4{K^8N?f$lG-x1C#+;ADJ)L6R2TM{uN`cJ78`+{!(DzN%;!Q
zsW*jFh&e{ZDCZkvk|7yiJjoUuI?6eP$}pvfDMnE}8bZlDhIa^E7d;*8mls`SfW*n=
zMneixPIR)xp_4A^A^Anzy}jRWuuPW}-HcqKnHk4H$7OPgJ4x8RNk@DRG$JQ#Q)Oso
zp(FIay%KSpUPP!rjGu=((g`JS%*9}xwGdL1L@p(nx1h>MM9I<#HE>M@V4cMf6O%;o
zQjaBwl1hQHR6@$k<1XZqYVb)(LTOUXi;yK`g4WUrEpW;j!DrTg|4s5)Ykq>0Ag0{Q
z+h4H%D%(na_*Tb%K{@tc#KZWX5zoK-yOKuse<KV3ggde7moBJkXn*XoO^4j4iWQ}$
z!=ON{en$qP+rm)KOP3;&Dbfaa$vGs4nR3{&x1givzlnbh!o>|~@N<O>VGYcVd;9@B
zdvFxaL~ojV-}Iik&AsQk%w6sM`FzI={Cd+GqK~QY6cIrcXU!R|h~i*-BY#YRKsR|{
zr1wCjrcldKzfTKSj{$QMuY;DFm3Ed7iK`@<weiGVTD%g5-TgU&p*z@})I-U@Ls@!j
z!^lV~r94C%s+wmb1Y?C>7BvL}B2s47C4tT=(N&K27Pr+b{4<<s2?aImD#mdZhXvbt
zbM&etF_RfjC2vYLKb!p##(T_`5?zLC)K+}>1fMh=Ri3sn!$a()#pH26izHyN0pNZJ
z!(JY$L!;Kf!tB1$VLmL&!)|OY+SBby+hI<@ZvV>?leISV5{k5%NVSy5`WVJuN|Y@u
zsFh(#f-(X#iR3h^O-$<%y%FGYUxGa(Jz{CDO%=6Vb3m~)sO5gMa}}AQx&M_XIcmsR
zDXgw(-<m#=IYn}=yldK5U?>w7qNKqYZX>hx+<EM3wrr|uQBzP_(>NY#hHQ;I?~ER3
zSBq2+M8z_JP4Cc(W9HmN7A5mo6-<AUhv8Q6d$Z8H;et!z+)-~(Rx4b`@yx{Y*|=__
zJgos%UO8p|!dqPCSw;oFTj-3VcH3ofyxc22iiVrnaLLvxG(9-Zq9y6c^1LB&$w}?^
z?sh>rnrj`Hf0<#Yxw<M8k99M*IwMPS(}X)BPuz9r>CzyKg{?_i)19>2kW0*QBm$(D
zlrBEFZZhx;&3cAG_<?aiiNI#9vCtR^|GX1EVbz=D*jPv5Au4`;u$FK5oEzQ%CUxJk
zlZtcS;2L$`!aZHz(mkKQqLa*6{F{`J>osC#(DF+^NH2;E0%r5}IUYTxX3l0^0;mK<
zz2R0=#RHoRd;qh_X(<bKoY+pEq+he^TH~bTn9b7^gB#mdfla!mZKh-|30o07NyUDS
z9M4OSudMzj{+wrsZ-o?3_T!xvd1-h&#{gFvQe9|^ax5*3PBoUZeVPRlp<k-^h^=+?
zDRW;-Vm=bA>p^o*DNfvRp+^Jr?<1=rsmN+@BXY42Jaus^eEK5=$Oebm6t|ahyzT+6
zbpmWV&9K;3-oqqh^+`D&cn;~Tr1#se{ND_xO29cBf!Q08FbEus2FW74b9?mT{S*La
z{=}ODs!_Fri+KLfhi=MU8JxR}t;Tp&1}dUp`?^acF~nBO8s0!ep@(lx;iV@L)_Ae#
zyDyM{xi9j!38)wbq1>|5eNhJk<jc4Eoe=)B<m`mo3BFLuv;qZ&!qm;nEq1iVM0$4g
zh1FN2?EDrxuh5qtyCiM=;t|0@2IH?-TWH%qp*vv{k8fe$qt9rKKMaY`07aJwFQFiP
zBzd^1BG$+}H$}aaB`?yoxvIV~jx4i#AFoPteypg8erFaSU!Ds(BAkEY4T>Z}8Nxj0
z2xT3pxJaWE7ZH)$^wO`$aReZXbI(ZLR_J0mIgh_|NvhV)?<oY@?47oEMuoOVWI7aP
zAUUssxFO$-mgT4TcQGU|uZ_2N#3|`sAD1pE_y`Kj0{b8=`36u*oM0H1q7{PW73v+~
z4>@)TEE0v^&_y^04|NY;SCx9C1L{*@H5a{eyG`^H<6S%kx8VOk{;SC>^<wN&{|g6z
z{yK_;|Jh{9I#~RdLl~C);jpNPHrAg$MqSS|>L{CmnhPVQ5$?c55pD{NObBgG@ll(S
zT_<Bzh!*=NDj*DJWSH<1^s_kTv<)6bvpFFz&*hdY|B*W2`};TiK9WMR<i0sbx+6`{
zRt1Gp2(74IB#-cRsw0t5;=EyGd^v;%_B!;?Cd&}>_9<NBh17M1zManic9Y)FVSV`!
zzsGK+U$Q)VcUxK3Sd|GB4`)ng`Dke2ax1>x0=}D}^Ko%;ocOj<S6iJ+`3Mr5U2dV`
zbzUW*>WC_x-g#%7(K%hBBF@8v=t?gf4T5TZpcZKOsIl*ds++ej?{V6wPHR{+W?nl$
zo@|xEB-~gNPlP39<2+RP2vx&v_=!8^CyibCCc?8h4xe4P>0BN+jsWxUy7IRzf~YJG
zHeOkxu(mKutWO8Sfe;R&l4NnDgfK70A@nhHF7wdnpHGM17P`eC?XxsLtm~p08Qxy`
z<#hQ=V11;O!23~$)OQzQbhW`WB9<k;b4)gOB{j=N3UpiY(GL$smJEJS%}VsThQauK
zLC+VQZ=fBO1c>K6!L3S}PjCx|`U@(5LsO*t6FsEXK(R~KkxD->8?RGHBXi9?^!!MJ
z<dsvj%3T*GHtd7T(0b<~$xr+YxAY5NXYK%lHe0Nx4{UaEOp|Zpb42C=ep3UQecw+(
zjO$PDr!Axj?UN_|fIZvB%N;_OzA2sp)-I+ud7Aw=ixEusu+Y>A(}30|mD~xL@?Xcx
zL);hMD%~Z?Ym?Akmhq(PNneCpwB`<5WCN67FUo{*qxWv#9lrbl{#TKlb-s*3hXew$
zM*sq%_|GD#Xyj@s<l<sxXZ&AxcdGik39bh6_v$LTS2Aam%X({wF=Z_628NUhKnnG4
zMYOj5H?{m`hm8%X1NCiJclB_%Gb-TK4-6#b0|HZ<YPbd1|5rRn--ez}8}-r)WG~09
z)$TM0%U*W-pYORZ4&a>8{zJ~FL4uqSWjqM`VX9st5vA~Bfb2$_X(P%=w9~Ls0=^Cz
zC4|O_GM>_Q%C!!2jd&x*n2;}7T>NB!#l12dVf5jVlP^eq%z=uNFHU~qh=o`e{>Z86
zw=dq<T@#!^<YBauFI70h*g{zS@SU2{cNUxhX7(P^c{Y=;%!$L;T`pE1?%3;{-%lsX
zaKhm`5iT#vsBRd97|m#Hr*TK!ElogYxEZygOIhVsh}evitHjSlndC1K(xFEdm7(Ov
z^f8Hu)ABM(7n5`}GI4uLj#gVG7T66c97fnv^eg-p*|8N_4|SR}SMU!Cim`k=f}CCM
z)_Oym_agqIvllZPEmmKa@KnD+zy&L^*ES>kYf<p1>T6B#d_ijY<~Q=t@|g4#Y!_cG
z9h%!c!@dRER)SjtwsSgM(G6bXmGG)ZYOk3M4NX^W?)-MCzj&*xTy`8niF+4@!v}0v
zHw)oorFUE2y@j~X4j{!=&UMbCzjh7PL8{}Ity4ETxZBLKTLn>D0oU&giXSn@R;!rV
zwo}GfZT(S`gu391=q6%6Juhlkd@!9>D}7r`F&S)TQOHl`(+TR1N^cH&r@D?T!TrVo
zXK~d9LmJLcBT050HX94q0V)DL4JR->xOE5sMXaMjJM{<+%;!`h0pu~4pM#sfo7_|g
z_1)Z-?icZxd#?b~;YlX5!IK|cmv9N$UD>&r)7L0XB`%}%_KBV<*`peo?%+;1_=aIU
zR~|QcvD6<T%8CRzP0}w#^x3Ar5fGjypwTfMl}5aH2}a^o;f}_r^M=^aT|Bi%RIrZ3
zcI~xB6z-y;z-b?(*NU(J*dsmm)o+z&pKt;e;!V{4#MQ8GrBMbyo@{!G_NTioI@aIh
z(E8Pwg37hJlI35>WY%=WnED(=3|x!fa-;T+5PRN=MdHQpCC~!~^VMpO)X)Qd8lbm$
zN~E3B^BAHzmsMkeJ=+vH0@uSHHU)>cWfT<DcJXL2>zQcny;yt{s8OFJmmO22OKz7K
z)un8gDCF`t>KTaxwukmqx5vVx`enp#qPtHvAu12yd!(Gfa^o?Zht1d0Ij#T%6>kw}
zXCU8F_Ao57<WjvQ2V^oQuw`!1hDi?T#^B|<2Xf?HsvdM~h^s9f!10cBm0f7{YI%2-
z_{bXO`a-unUHicOC3>B!s*c3n)?E(xBF*36#zPNG5U_+I0Xuy?&0}ki4ZT~{TPn>V
zN!b9>HM;CjmAfGBM1B0qW5+N4`}sds=Ke<$UhyX+CcM2q;vU!GOy|u0B5<W$rR3t$
zG+r~MosJh5fucAQkh>(6IsGnx)M{9Ey<4-28(D^pRXQ)5UNH81mZ1H|-xqIgOj?jU
zk6mL_bha-CLzTLI{SVe)SBnO;R$}F&yXL|5S2asnM;BB6D3rF*XpU>{z|7G{pS#?X
z4&CA{hhLs>HPjmL<BopzTV3e_?&_0FH;oI<pF|q*j_RBdOXz@l?<&GWtW!8wqT$<S
zf9b0FP+&M`3{a7vAj%4=E+?jh*`B!Ys#L#Fa5Af7&X<CvX4myK1lk{D4Pe8JcJJ(e
zWE7NU+m|w#acH1o%Y3Mz;!Ay4&_!p%*pYIR`;F1BHUxd^f+^~jnO`VgoGjs;044$8
zGpB>uU6Af)6z*r<$_melrl63gi?s)<lHhcgItMMpgG?ieU5$iRct&ZE3n6Oe8p7RI
zA-gcw?DS6Yx<6&c8!_N{-B1;jt$%4HD{Pv`&JaCIWCSx_UyT4tq1fKH!6!O?1_<ZG
zm;PvIj1fV{Bap#^?hh(<iOsC+RJ>j0YpGjHxnY%Y9~DV`QE({aJ|R}*mAYe7WC?OY
zR14{`2-@rBrKJ2ov3tFn2PCiZuP*6`k3q!Eghd|np_64Rq&WHdxq|As{6MW)n1IYX
zKB9F$jjMTf!4pJfVom1GrFF-gqI;WV?t|K7`azXvX>4A`Btol~VsRgXDYu95o8Na`
zWRJ)I9C*=Y8KbDx6a_Ke=|cEJFO=mnbM%E-d8LP}$1=}2R@~AnrIX<Glk%%shd|qL
z<PoWKRE_s?Av>Qqh#`B^xIFg#jNlsiB&Ta#D1z^j55MqqN>YQ5z}(bO)kwUAxy*bt
zndsYEZL_VX&4^%bNdhaPz)M%j%Wt?}HEfSF=uf(rJTr5O6q3*!{_tXbp%Gv5*|YkL
z@T=$^pDB&!ZC48UzV9LHc`kBY{>HC&Qbg+newi|UiTX9o5U(7fxQj6SO=0d(Uq#>@
zo&fyYN6oQ_)K*`#$v^*=7v|h;+rj;tC%>Ws0wVg)7ps)Li>r~X?LVSDxmvKkXveJr
zl-(N}v_mvVgOfI*Bwi65I7skP3F}A+cZ@_ArXEQ#SEM(yNussd(b6k@iaHDGHSxxD
zYD!Y`fOTuXwwJ=z*47nu8;8s5-rm=j-K`Jm*8p>Oj%-t;Lx%n@^An$((?2=4z6SbV
zA4?KHEQte<3ixN!M=4`TVhyr_L0EyUMkmT~3YZD%@4yi6v**A81E^-UNvz4By5lM(
znK=6-c^Cye9hzC^Fp!|EsTSj(nJ{w?k5@o*Msf#BpsqM`@ORj}3f|HsTq+0ez*$2_
zpt_T0z*R@i?==Z!%2`!Tx-)Dr40n&hVVDy!Bfwd6G9>|(`RNlbosm9iF}e5!#&yq+
zFkW@E`!1epfpf=?AfDAKo^F9@A(*2VrB(@LN`M+<n9~oh+9y^j-=M+#2bbpjH7N<1
zz}chcMO&!5upXs@RiN7Ad;eXsxtT>(a8FnVwKtNmEz`v|pxV=GVC#cu^j+iv^@FX!
z^tX5A_YT=C>ab^^R;TX4LLj?ScY%m6+qX`UU)Qwz^z35QQ(rwQdC15VRgScR_zh%P
zZ=5$LG$m4i9JqOT`;^h7A5>u;RNJTp_L;b+`dagpQTo{X)o<4CJ=(kcbo`y#2R0eO
z@Ub=*>>LhVErpeCOQU5g*&J-O4xO$dJ7ul1VKeEM-A`GO1eY~dttjR-F5pXVzddQK
z&Y5hY38aJ`Y%+ZlJuS);4YL;T6kJzbDV`jME6%0Pc6P*z$~Fjwr2{y3QKN^S8JBF^
zf5^d~I~^?6>gc&mlpx#1LmhY8!?ORH{aLgWv#Us!%Ibk_Gaadf34=ZHi<_@(t7)Y}
z$&&W~B;m1^)ugO7>O5&Ne&O<njjMCrOedq~`M%*NYvV=?HeID<cj9`9i6So&;&~#S
z_h8DcYx&v*bFwUp9#^yj39=+16gk`uGtP9$h-+IlgibCc-lmaz8A_Mgs0qy^UU~LC
zPUDXbk`$g<Rrkq~0wt^~RC6EcJ`PF}Sya3SrlG2ho<?YuDChDP_w(p9dK)>hObQ9n
z=kOe%uzC@X$8md#Rw@k8+en1sK}H#Q>nE?`NI@hqFe^q>E$j%{g3TsdmhNNRGH}}%
zd#yCpH<mjT2$4#tnq1O9+lvRPliR@)OJ+~ZjXw-f@wSYT=b}l3&AFD6#Q~{|wS8G`
zffSA#hVD30GV1P75K&FMS;jk5u&(fYQA*)zzSos;2txk5FDp=eFZ_W*?XG1YjBaFx
zS^UyfD=*aZ85>rbZjE;sq(<&f$D7tBya<wXScQ9*&*H?DoNFua;>;<G-S~*!L*^hC
zvd-#;yvj?qo8I!c1i*efnSgomX+5r-Eo!aqsYE{fm}fn4OABS+<`&nM7RnU}rz%l=
zhJkSJap`m9ttIdYPikalThDkF@9x+Hv(RhgfZ-rC)uru&gz72{1+o)j^S1bwY!2gL
z#srHZDoZvVlde>0tYSUJq_SwKGD`UBM$Cey;V9e~(Pdc*@bSo+#N{@qDN_v6Gmi$N
zP!1gLb*V%t8axFpEuzhuwP94Hou(`3T_|OoGuL)fzEdnW5fb_dcelwH&Xk72g_H$U
z(_yUe$LEcGokZ}U-Xbc9v>&P*G5I{?`((kb_kgn)5B`gzg$e?ZluAuxg_<ev6<K>W
zll8KK*76oxT(lTU9ak+aBzBVUlBLk-Qbr}Iva4&*hr=nti(q4D(D}Tk9k#n6VSoU7
z_hRUwi>?XP8uGjNwDgmipV1b!j7>r^j+tl@8eZZIFbXF&$)(Hhu-2JHTy|3v#n3t`
zt!B;$XA@d<wk9dvPUhsdC<zw@5m#ft?oN7gNJf>6o=bAKD#EHEU3@Hsf+#KKyj}FH
zPJ<z^O7kT`+Q!XjPSk@prew2XmY7Hl1`N&X4D-;W+sD)@C$?~O==@|O>SS#Ya|=d(
z&Z?A)O!z8Fp&A>8_EtCsL+S`--r!;5$x6@eh=^_)bUM0;yN7*?sU#g?b6Zo#iu@_U
z;mT8wb!OS(<5RG7f1!sOx9k`7SB`(-A`xHlqT3U8YF(j?ns+FH+PQciLClz{<7ClX
zRZF(L;<@+ln!#?hz90wHcZ%KOyVGAs=BW+`I%?m%dr{Z#!_qULHBx7OLdOgb=>=kS
zNl<62t!`=+DrnzLlRoe4VD2}eIga4S-a-dkYJDO7MGqS9@~N-)dgJsrW+8(f)t_wN
zU6ZeO{;9Xe4w5eUldsVzh!vkiUvRiT=MQ5mGt9(eZ3oS}u6%VU>DtxjPtwUwZ4NpT
zmyMldM1-u*&1IKN{4&x8{BhIq)N9$wI1FZ@Z15$2Wi3SeaW9tBP0wCdi)S(o2l#y)
zpQ*oR`wGInBuwrde#!F414OetP-qXepOU2t9)>>cQg4Ve&WHjejwKAyZ<=W6SWL_H
z=ynS`C*})>gbtQujL93>2bSIBRd1KNp7g?3?Xj3<7K?Y9ENuA7R@C%Rnq{6uRhzq9
zVPgwtJm>~aZFYWeVrcu}(C$$7;5Dd~{#4H;h}g_puFc8}bwVj3#Y0Ua&&mt5JP(D4
zS-)DGYK;@+tvb(2l_Ve0mxouQi?Zq*DGP6^Qm2th8)nW_N{&(t&$+1?5jlUTTXjbp
zw{&xlWw#bQmH>~9uk?*1)OdqV%|{y}Jn_F;70GO-Pn`cC^Q+<&6i|7G5-5FGdSHjj
zU&s#rCD@HE16eq5ifubjS>+V|lU~LDG@`4>X_+|hSSG#dllB&wT0)I~bdKs%FSVc2
zkd^@7#wtp?+6dSv(^>wKpz5?G&a+58`OHWE08{mwUm)ejrcxN5%Dh>%`>3jaq5(>!
ze%eW@5ym8jH+BD{kD^MX09l&;lq{}(L**xECi};c4SU(cZ%=BJHW5BA6!1nJhe#}M
zWyi9KPEBJJd5Pgne0B(*rwsCij6uAg2HeK%9K^_gds8>K<J?JsUu)>!iIV~+`4yik
z{-7p&^5hi{*>L&&BLWiG7uw$yPsD$O58BnfOAC%PKIKOjiziuA1K<fC9l&^4B)Cn!
z8h+b(+e^>lqX_iS*n26I3M@##{82yNyMLzcpYtGT&-2s~e9t~lOpusqx4eQpj<K+N
zJ${2bZc3>vm6LnO35e1F1K>GL;>ianTWyT<1fP>q9OE^Yr*#q3v?g1Px}Iy^i1IJQ
z3Lii<UJr?=_ZAwn26}Bwh3({bJYd_ms;JNQl0BZEAIaukZVZdFzYHqEU<;V9EQ@gd
zm^WI(n>~R6xA2|TgP5IG+*@V92>yoEn>{h3?1alaOzKOByMuzIXs@TY2^O!sX|R`i
z4?%z|>vPlwbFj+PO_C+Z%e?X#a#Hubp7)bdvP!1e_2q1I)z)*zgJNiG#$&WdS&h%j
z?=`OEZLG6j`cmg59Sc1`=TBiyj$N>al@K+E$W6O;nFd^JNpz2?<&$ts{3>I%(uYR%
z-fvPG9q-z*&<#S%!4o1Ml9ykZHQM~~-SuM1o74pNqx>M-l#m+qZ6Sn?<Q2vcv;>=b
zR^I76oU7}YhD1X~yxz)Z{hqV$YFUFwg9XI$<D}SjId_VaWg~%VohX<foMn#lJrz?^
zHdy}Io0dR?3ezbiOZ~zb+VE7SfZs@mc`ZT2gD_dbZF00E8>3DC!_-CkZeqI;Ou^GR
zmDEGm&@94O9uED~wE93JW@%^cwP+=!u<%JP@#!}?UiS56L8^)HNrepgMEV8~?gRnu
zVkz}fX1Qq+I(7~hFj=JzeI<Ll`JdB7mWIJu;F)^@GI=x{5nkuhjt50WA*|j@9(g2r
zxtC|nMW_BtI;95IHLxKEvnX9iBxOBsXFXmcR`D1BGYb$4z4?>&`CKBdIlDP}#zN=$
zgO~?*d*e@Hj<~Lx%8AyW4bc^-2WC~cbd`amPE6MRh|JwW<tl`!$V&X+=h9tK8Fix?
z1b$gC$DXmT!dy063OPyFlyTFZ@;|q6&na4b?%ePJp7%K#{DR=eA!wE~cpRDf1Vqn_
zu?+Q`veI71Mh~eP_+`)$wg~wmJ~;35vm?^6WsBGeMg%iuC_TAS93~~(g;MSFwW2TC
z<HIl6lBpK`KXeJ6{PX$(2@+4Bz$JgWsEG;4=|-3<%e*JE0y8Ar^a?o$ps$`xnMl_n
zejSDM31w}n6Fh-G($G{vR8-`b@kxxt$0jtxai@hilbhBSVXMMVV6HpYxpkNw>xvna
zFbEa-a%cC+`UsH=%AB#UuZ6T8yYlQn&zK9&`MF}6&y;4ma_ss(vDLg7AFnk+oT^C>
z+6x;1k#eBP3kd&o3vt!f83CHHyr+GX&l8<{vw4i}@%pebS7YqYH>ZEZ@Ve#tPM<XF
zD~FPv<#zlGne@-ngQYudflpAc_Dp+^{uJ6<_^|wG%1!Wu{*Gzj5-ML$2Ziypq<h=T
zN?#$XJ+EM43s1es=5T2*K*;&+t$1FZMBH3cIAPf?d=+g;(h7{TwLf^x8bM$7-U1rl
z6*^0Te*(4ej#d_HI&ZctMB%oGZuv3qK__O5>Mr<ms^PajR%IZ4LFp5GSNvy@>uL?h
z{1+n%2}CtP0VMH==%(0S2`HltG5I-h&0Vl~XrCD3P)+r~^Ooo1L1z@gqQ`!jE~tQT
zd>QZ~oH&>@-Eo7Bzs!n?E5#7U5~P*Cj#1^S7PZZzY8wG@LH8k+I8CDTOL;`KID$`J
z(FLzG=y)<{0nI!Gkqb(J958=(MV_}y;BL}N%LoL-mP7nc5--ipG=zntf>*E!Gt_dQ
zJW+)@`G`t^+NI`(Ku5b8@5GBK8pw*WRUPsQ14m3c2qFx7I^B}>B8`?duZ6~rR=WPG
z))~yFDC*Yt_$8E|OUk#%+U#h}E_UU*@ZoFooSeqgButT-ys$<25m>fB4-Rc60}=eG
z5Jdj`=6SIdJ(KFqOx5P3d}gP3UZ|g^8x9IvPD$0vM0mdd&#1iQs}~SfTn)ZyV6Ph=
zmP`b#bZQdmUVKvz(Ma&GiRx-8{S~X2PtQwHekJM<ehLedyqjG-gp&U4&f^@-cERmJ
zwOa?-JQXQ@!42QkV0|I8VVaph4B(r$?IEtzY6D#ojsDz@{?p<+aBojsJ;%|GTHu$9
z{!bxUo(v3^eC4N7PrhsH*ROXzdHRlvRQ;gz;Ael3BDpOHy%6D*opi(ws(`sY@Y~@Q
zz3lodD(hxcI&kV}Y9fyI`@>g(tz93saDH)g+<bPQ!I5#AC9Do=%7tM*EtL;1cs{AP
z2-NZj+CWs6+9j;EPkh3Ix*h=8rN}NsfoEHzGwq$UY5$2<#Z!f8=Tt=et@B}Zss0OH
z3JS9oufPgn%gz)3v`2p%WIgqmd5<Zn6AnW^I9+7E7wY~K$vKiYUl74*z#SUBcn9%v
z4>o!yLhjVxXVh5KkM7W)ZMB7T&<E*sjkwQ2NcL7@m^TvO3H9QS)j}}zf!gq7^a&3V
zHwHB90o1d1B;&gU@AyK5)33lVc(G>m;q^lwVvLV9S|1qgYd@_(<DAX2GtGSw@hsx5
z$uxECT;hz!w*PBA{3SwbE&!?`>a=_w_elkJQ|!!ZDBL|y*SwTt*6s~uJzw4P9J%Yt
zY4M}7x3h?GS>d1u60qkp@4|@d9mXDCJTS+U1<@i0X)PLJg%HN-kV-MH<F0u;5$2UO
z7m}dd(AhH>7h%mC<Cdq^Ox*~bqi;V{?Yv5|Zxh(l6lAFLfVX{}sQGg_OhlOT{tpS<
z6N+YUjG-qug%3K_6&u-Sx)t&=T47qC9<+I{Tz}{~DiT4`JfEfu#TN_$QewKrOlyw6
zT{ew^{l`-GuGl`AP=iMTw`u>SYr+co`9{i<?cuR=II%moWBgUI@j{6*f6;ttzOmpr
zZU`FWaQ9YtEc`q=Xo?^((v!3X^BFFY%zhrX;1T|QdGKT-=$Hd$S$D~jLqGV}7=lnS
zzAKlZ(3{o^OQ5bNY97Z=X^z-+lD)yREmml6$ijEtGfil^xx&{3raj2gy^vKxfczgI
z=>S3dFH%dtQ0_}Yp>tAIq<~q9La%k}z2d#WHlBu=Z-i>{k=vO~CkDTSUCx)_ssVeH
zi(*8f6;SM#z#&3nABy%iqfdqX{a>p(^OQ(bnO9<R3(87i+C_Gmul8ArT4ow3e2{T}
zuno_|(8|RzoC1VC928&b0cPK*$J={E7XfBLKt)=}G6k!^I>RV{m%iTinMMy=L_=lS
zKc=TkHId1mPjdw~k?WCM1iYyaFt(Q8h04Pgs5wR~%Q;j}3|8SVUpAW*Frq0ltljN_
zZwBXkOT@|{<<pbt!@uOU-6%Lq8s7v8F3(2#RCGQ$u*S7?v*khfyFVQbuE9DPs#E6{
zU|hemzsF+ZUiY!iKN-FZHm7k7@uk@OGq9f<P7`Rz%PsFMsNRJpHfFvWnwzsf&Tof2
zT<*ALe_hP-dax7T&UQO*L)S36<YhCh=e|J42zk-+KCVY|3qIHr?EDm~fOV{CI$ikl
zZw28y1F}Dc|JI$P{(Wupe<=tTc5rZYas69ZC1+%1ui|Rt?E1foPPBBk)RFiGo4~73
z25gzY96`{sN90wcaSK?X!y2%X4AZzvq8k{*Mbj#1QP0xotv+ynZ>IOLGlbXs%I$qH
z{9X=NaIZ5B;dod<^vKNQahaX&HTim<b6^1O2_iImbJ7rJFemF_Fj5z%IWgTH>WTTA
zU@(#jhh)N@(mWXY^5)%7ig?ycMM`HRD@L|KSv9jYR2hVPmUQHZe`^?t7<+zG9F=&}
z9He|!e0SCn$4o*|2JuzND%@BC;Vrxi2XY#fWde?6nlYs5oMvxcUAD_5`_9NzeTH9I
zeCs1ZyVj$lA;M#+b!D}yq{<b`*S`4XZ!YqXTbkW$(Nbq4wXaT_sgVNSr8R&#L$n)t
zSq5dRsut(QX{Vc!ROmvPiJ7(H&UU@4P|qT1S!To_>Lxy&fivp-`&dCRq*_mvPB@<x
zJ{y0Nr7LS@RF-W-63bBmYdWs3epFiZ(=l9;hXi<ryb&K6aprkydY6qVB9|NjV{1Wc
zF|Lcd$(BK3d%>T{t2WiXJiM&)bYqBYtDS9WTzbEBeIZ6wb_RPw&z#HDTNvG|%9Q@b
zQr=B<>VgtdN6kAIy95aXY}u+M;mCYex{2#l^>6%+WIH67sE*1LvK`D-H-Q^ix>Ecn
z!Xk=0y5)NEooG;83`hu~PzK5ix-X235QZzI_Zg1Zc9qx@$k8~T#ats`{2*}taT&EX
z>Wa!UN(5N$^zdWLM->`c0)~I+RLnGtbv|sZ)h_N37Tn;F27K<0?cRAP9%Cq8Je&a4
zOJ<M*rf<YU_39C*nrmp4`Krbc#N1*!Cl3av41c-uiXC>doAyi@3d0Wq^R@<ptr#G0
zm11YdGsXu73E32a3_f?y7x5Aj`18+u%UNs=D2>ps)|qEDYF8D2uJ;Zm#S~4eVnd9Y
z*64UsEy>!5c6(VSzQE)rt;%;p6alpYXMNHJqG~j@>aAlVBpRunV-!blQdixzwrr_|
z2UTWsNAY1_D{T$U@qY7kUgiBKk4Qb#TESA+-8ZE0%1n8bVUTts`F5R?dG&?tn_BGA
zq!_pLW|os7e<6==HEWE|-qawP=z(=&U|$rwa!5%sR9Bwv9Ig>ScSVMbq_^k+LO1Wb
znPNwks+a|Pr7S{_V9UDn1sQppiH^C7NRu?44JuEp?%Wr2?d;Dg;`gKAK3(kSWlQuT
z?fex!clcc}hTB>3!YLHbyh8CIjv-L}l59LGanoVM9}1oyCo_eKCd;)sgULt%5gB(e
zCkHb_m;ym55?@R>6vL?Fz)bOLAVmffM~k5x`_Bdxm!qNO;Bxo8S(LuO`GP0`gDkQi
zX)~Az+6d)i(5MBDxh=PYjCICvJ5Mw7B7{J_2-9Ae(!dl3VNAaS_sBkwgRh1}Viu!5
z@I*p8qFG-LApBB2Cp1v)59OM0XcFAr91`tw$VjAiDHxs<6vua%#GV#<T%%Tp8~U%Y
zuDx7o<9l*mVxsMgU+2#7txz4DtGsmYAs@~@q5A?~*G`M>ruqYNi)aq|waI*gZ%m#(
zoz#QeVPIiUOLhRrJwlgGlYHgeaz-Iw9>L?DPMH^fvnSkZvcLAoYa{pr>yG%ef8gzE
zNvGwA-UaWuA8x-R{%ZnXa<!do>2C=hDHaeA;6LXX{#jxw=4oc)_Fr$zQZ>8`@zk*Y
z$TdyavoD3(C$&*g(URLO&WKIzq>)Og;Eb=>E@l<2PTa6+tzU(1Y!E=f007iQcqC||
zVzuo;=Ma&BMHnNvw;%lXgP;y~uQRgj&0BWx0aw|ty|2BuZ+>GwAHQ4>1z-$&Q67%y
z{I`(@bV2|>bN#o`MX?be3is``I>+MM!5!-f9S{%kJuQ&XI~XFR@t%(KgjA0V!MXP6
zhI~vp$%cH6pFT`I`x|_T0ud))MVcOrGX2N`vEO$Yh9p4WGJFXWu7{YXAQ)-(Ak91h
zff2_%ltW`*o@9X%BT-|aU#LPkaSBnn@l#hS%pa~m`N)KEZ}*{hc!{|mrY~9o{FuWV
zoLB?N4`04O22lIaz`j(043KxJKz-Cx3h(!=L|viRmk(dza0+UN*>7o*?`?#8&_$Sg
z=;V8_haYc2881Ub{-K1B_o$z&f%#MwdyBaE*f-ZW_~-a|>wMhX?LL;CjujT3rm{j6
zx6F3+tBK3XsQ5}#vLzJkRGN!+C5vfkP41QxF?EJ!d4YAamhlq8-zSQv<Lq-MQ9Uzf
zZL>SLv%EGQt}O;XAM|=fx{FCehWNrz_;|n%gO|#fYO~da6=*b1GV&TxCgKXWxo7IN
z_cya77r&_^Sd3hu=n!s}rqTTHr!|+bX(%Vf3tham6-HW}vKx8LOJ2w*&}uGOrhmji
zt3*>i$N80sQ#6~DKVG+a{Y|8i$DkpuTrtVwxMmVGw~@)lg?kD99GQ7nN7L})<>UK!
z)(ju47+kX(KG$?JASp#OEgN-n5sj1Kjm=2gF3f~3+z|_!X$>bXbgLUE1j(7?pj3vw
z^aVdfZ*4_7H}Px`2@*DP%e&6|V)EM*8?%t3!0H_x;p(#8TrOu**-MgS;TdBgF<q7g
za29&y788}~;1>_|qSUk`GMT{M>#swfz)61GyNvEFw|3AiVzDJpAMkod%a{HQ1Rn9Q
zLDU5Y%2}nAW^<L>lC{k;s0fMq3Tdh>&L4{8iP~wSWd-XHB^o1NY^utm&OMc76wf|T
z2>Ac3P&iA&L=66!+C!^4zxXMvyjs7NfZ8pS&A``1j+VSkLr0QH+qGtfg>k)9_Q7^9
z$pTL9G+&;HDq2z&iGY*nC`xU~nI{b1dL;IXuvk1gYcR%fy$xICsWa)WGtsbTjh)bL
zyUX~c%08cqvEWCFOH__dO-V<e#1z@-$8~3PQcHD3MPQ{tqS4xqW*84Dz}97CjR`h(
z*-E}-`9%A^Eu-~XjLM%@@aHx92lSfnKdI7~JydlCoq>DATe?ktg(B4%!wi*OnsVd2
z^`?>)Z*2ZU+<K0loXJfnD&BPs=q+9BVVI#2uk#s>OIfZeoc0N_*y@^lbBk6MGqmG4
zc<DE1V-9M(v3oxT_nNISwfLyrkIbk?8)QaGJsYFa_e-e}_NCFOu&BQS1(db6RqFM$
zwKeW3d5aI-I;2MO#q<cq63peQ-l82p)z~k4vKORc(=Az9N)H|4VzSkEX>2c2f1Cq~
z3wdz9>AU}oZ#jbfQDOfk$7K`qW=*_eXP)SYO?zs(>mwP+8cl(>?H+h`Ku>%7O^Ezy
zz*~OkHH$W2*dBG-dQ*b+`TO11Nv9<$rh%Se`m|1#1Ur54#bWvwBaN0CT4`wJjuKFY
zN{}=z-vj;a{7lRB0`sl4hq4L!l~kmm0Z*Y)sxmJNqPV|<#@(CKQq(PIbSyc3+$nu*
zLtYWJGh3%PM{9UCOe~$Q3!NQ|O{M4eY;ddG^+BQ(Uv0!IdD6sP2Lbytl?elS89eC<
z0fF=doDXNRyIivUq)n|Kyvmc+$f?F8Sg$jBJIwb~@AE~cF_!#DJvDIYU_F>xsWQwR
zI^$<l6}3zXb-!{gH6=g>-4y}LsJn9>&xYBz(|<rIJ8#v**ggjWZ6?R-N;8DgJDJEk
zQ1Rxq#7OQ20`I9N&-=<_**E%FOl6bSq*L$8DtkLs;{kz&D`%?LbMARpSw0@H+jkkY
zgQaSaOW#wG&VDK&7g}Am&z*i~CSprpRYjo})2SGm^1jeBdl3b)tIKns{yRfQC~q@0
z!+jZiNq=y|DYTmh0h#NYjh)#F=fhcH6ejA&D1}?)5xFzr<3f5<&J@|}s*dN+wd3_-
z`Mwd)KqJ~_xWmZ~>z8O%p{*i&m-dD6FDvZF&c=7}(qScs!A;{i6Yz4cQg;Pw^Ayas
zyr^?8^W!gAE$xJd7a3`87Lirmr(DZZwM2LjG#MO}w$w3yBc>Q8W}TPft-6>IezJHN
zl}4GC_2?M)QaYZ%Sh2l)@S7vF?~htABvHOLlMK}qRp`}Zg8O+I$$0NGh(#XWr->2|
z?=uyt{&A6dF-d#(SrO;XErZ?Lm-IFMezl6gaHqV;L>xgb1z?)ff|!{?Q(6@2+%N|O
zGm~b3LuOdOXd3RR<}8aKi)-9ej>@{pWkRN<k(wb1B@b}_%nHT?i6f@^bfGaV+7sVj
z=W<c<NvNV(^C9Cb7EMhfOgXL=ZBZPHo7Z|vcg^&qBCMwquXWNr4{$VTYl@+UrYE;R
zi8s0^d1(|%LKmTgYQ+<?iGkxrX>ViYhvb$B*})fWrbXLcUWooMQRI(7)6BV`W#hQ2
zzF|YjWkbnhV`S_ujZvLDqLMozp6wLd+_tJ^)3la_SZGu{7fyOOut4It{9(TEu>R$0
z0)I2er+Es}__qe#J}~}rg%iJ(Gek)MmGXeLE++(Pmb?|YcU_c|eQ4OL1Nc-$oU&9m
z(8r0m>8uTyH)MW0`nUrwU4=kM7)6CWrJ21ViZ2<Cy3qo}f$G#3+c~9T)Jy&a2U^pH
zP|4N@<k|g%H!zEKvvct$+~i?PW?GnGxUHQ|<rhgsiz^%bJy^>^Yf;QQUo4GfnAGH$
zL)M47{HwbUJkq*I;j@-4XK<+tXRcPaKZeEh;WW0ko4OGKywb6I*;!<*vYTiJb#D|i
zm)IQh#_=zB={>wzbC6tA=v*0iEn7IdLnLTB_sU1xi%;GQko2wu5sX~41u^8Eui8R7
zGx-<Ps<RA+7XB=;IeF#QS}j{YD9lvDQ)1PZtsC1ds=U@nm6rO@G;?RF#<Va941Z|Q
zl_8|(=8hv;hh&rR<KA?AT{(x5J*clsi=wL${)byLH_1Ej7hGL9={xJI_Uv&G9HDoK
z{i1tmW8woHwuoIO5MQSN*z4ZV{yq^Xgz@%RFCF3HDn*G=?$K6y%+ORYl`bLh6?G+}
z$PLvo2haly&>{BaYG<+D$ytGO@>Wl-x+Xo3>8>n+zU%GprXT}ovw_Om??L(0`%s?!
zuB!P=o9##Zn|Ed|1J5_=xr3(d0~@E@XsIM|nRMt@?oCMc-<#SOnJ*!)n5KJd!w=eN
zcW#{^Db5)T-AMXkPv}1Ge5A_8bNV4`54H*Bdy<O2TtH;s|BbPCim$xewzX5SZKGn_
zso1tFwryv|wr$(CZQD*ol~lgGYps3GyVl<4`|XQ)H!mi}=>OK+XyfVc?E74`YqUfB
zg(CVl97VGs7rdnCqvo)?a4wZ1^D@<iRf)>y;uf@IXQFbs(aGN%*d0_2COX%W++oU?
zIuvTv*U;Fk9+!Sc=XP$hFL;0&S20&y3yTE3c3F#R%(kT0^LGR!s>^5)b*ABO_D9^Y
zkxgE0_6!6X8crt<XVOMh(=vIY$iYNYQ|hmZUM%B!2wneL)MMYzV`@Q)dA&S$swn}+
zy?!EGk6qZ>Jy$g=xZU~l<KKmG5O!fj{g*Hn{1V1g|Fbau=f&}V$>YDgf`3JE)FIqZ
zN6`L2$gVF~sBl0P4kUuWEXGdzMb-5pY9JHFBIcU-TX$xnpWU9RZeA(uCmWQkhAoKK
zC2;V{?xSXMkgtWyT%wZ8x_aD9opo`)nz}l3ZP5z;>_(Wn>U96;a=(F-<9oO*09uZS
zqH5lwL&LdcYU|XdtC7EzL<2+C_EV$eI2ft;aEsdPQXRUmYaw`kx$^+Cl~*9E8^0BG
zcdH3!-<yKo&d;C!)}t~6-zjtN)uFvjl;T?6ig2TNYXf$3ci;o%Q4@aTmJ{$?p&pD-
zst#y?#+O$hY1<4zpn8cVKqxOA3Q;L!B<dkMx{uYye=;18i7CB3Kx*?<?&Cxw7&4^7
z9~n{h!p)Jtli<nQRf6WLGGNYDzQgibj+v22pZR?U2KF;C=VyF~+}HrUl<1dOMjJb=
z@bQxATPdP-9XrDD*`)LBIU8wF_QKgwxYha5O&-=$vk&o-r|na?Kk^b5p0jTX?NgnP
z(1TdI?w;G!RdHbFYdhjU(BaX}4gY+3adEjujd;pcoHIMX$jgU(-Gn^%GK%gW&&HhH
z_}I#tJE92Sr-<q7DyV5{s3_naDCrkj+u2#0OYx`60GlGA+Qg0$tFi`$xj2Ua^|C%@
zC`xh`IT+qvTty<8D<KHOQj}oM9|8Y5D8AOooxzSG16y`U3yZrI7bJ+r+Dm})_SzH=
za$VmLFkPt<J75`SAO7i2>Kt+}(_C~BhIC(X%YiPhGu`nkh`%fliFJTGpE0nc=m07q
zM0HVIGSn}gL8gLNa<ku_H;(0{j!SDsoCe#5d1UzUrIy7dCrU_E1c})vAo6>kaT{n?
zNkTGXGd&4agun(1mOI69E1K~;kMOz!py4!BH+xcF3WM{hsM3sv2PDOXtMjewlFl*G
z1$}rj4yo)?L|5Uo9zjCwSddE=D=yI(xn~&0*N!dO$#bMEl+ju?n2#s(0>nSbxuJm3
zlN_Xi%K$e@?J#%cWY{6DLZ&(LzMY3fKz9O9Z?m@l1A@y_ZiMzjSyX@j#ZX%7HA?~u
zL#2Hljalz|Je%lIV`OH9TfczaHHeA?rUY|RC}x$!KIU6$?|!6B*4<s=k)K34o`i_u
z07PUc5?%|F8QX6wTJ}X94w?JO9X|T}HV470rYXDYiTF;K4H__uR`3-I%syIdn_(I3
z=shyk0vMg|a6N+yPAb*hSge1NxC><{4cMZXC|Ta2dsJ_6;ChB`LLIepcipHgW=(NE
zW2j5_o?ik1KlbII|5WbLzfdPw91C8}ClqYGwE}wfZm_?|A{OHN@Ngw}R&eOo%D41z
zpToYO$sVmWO3O#;kr>klwOc$`F==lMmVS;7iUSY!8ISwS4O?t7b#g7DS_u+{k!Y+$
zcYh{=>G-Q4?o}$yB_eRJa&)CyqR<3s^vaD(Af}utGEB$wjXLC!_+(H+1!X8AOK+7}
z6@oU@MXU8&QCNY8*1ij(4aLhEwx!BNsR@UXNs6QqkF(Z^gQ6<rg-O<_BT2C+kd~h{
za$Zw^@FvhhDB4qVnHa$rVGD@REDee%{@hVWFAFm^XzOie<HcvMnynRZjbua;j7!ay
zj$UZUD=;BGEXOf8fy9$ZHOzy|*hJzr53*SKZkb_PWIhvTC^|Sl2wkTr32SbUgo**X
z!QMu>r+uWsr%6j^V)mR)ghP6mA5>fcsv<rVLRmClQD~VjS~)kUsX$#D#<Qu36237+
zmWazvJXfG#$blM_6XM%Ie9oc}W2uqNI}Ss#Vp1&5z9F>0XMe;hWr%}1R~qJ=AGV?p
zYpsrwvdbn?neu#q&b8M$B&=u~dqsrKEcY~G8~T9#D9s*~-v0K=vMso<^z1Nmrw5PD
zyWs2;UB7t1M329eP!$%pn2OXwSEvc7$%Kj)6;p)Ltz>mKX5YbFyNA9kGwfb=iw4s$
za+x!v#%8R%tXAjUs=J2(8_F^Stxgv!7~St5Z!O|8r4K1hT%xMb&85Rg8LsZWr4TT7
z$AEC;?og_7@sveuKC2pxL6~q~=*T#dqiMLBI`ep~yTup5ID)4P(qShztWjm$g6EMl
zRq@gCGgwufB?{@RA65!lh~k;)Y!9YA*?;KZo&bZxr*Z7Kp(B%*h8IDboP?1Byt*5k
zHfHZyJ2B-^G^Efj);^s(7%d_XyGf@MND_|)PB}k77pyR-asN<E|H%eoop3c+7q_fo
zg;)u5Rs4{y!Rrl?5UE3Kkp+LUFMk={#)$i4v?YZ({%$RK#Q8Y!y?n-X7+OqLC#{T~
znVp-q#d6}YaQ9lHlqR8y13^i4bZKq+97iUVJj;`VAw!}BrFna_RKG_&@_>?8)R%Ue
z%oY10`7Kabj|g)CYlNC7z<xAvm^wCs*X)Yhh&-0{Jm`XdyHxi^#5MbF>m<@)$vOK(
zQS(k(fNv_~_SJnxwYxu%fCMQlt=^brGOM5gByQv3-hw-DAe(*blV@u<)#{h>hhQf&
zp2O8U!z*FIz~<-tEw-KOw8xf9+A&<2{czs3-UpDXK1lPoTcXgf9JX+GdIuQAz7K_E
zje{?P<<I_)yh?9M${??0yRPV1hQn4kR2FG|9=!tGV@)#<wOx<u^gtjyu@|i%NI;l~
zfjwY99Lm!R8KxbZyskDQ!uAFg8bBt$x)1X<FApBr9U!(#povLwj&~2XxXlv2Fl*9F
zimXl57pO!*V(cy@P|H702mOXU57~$+o1ZTNMHPfDn-@<Jv7jC=suD}SMEfVV#~fLp
zO=DfLkxrc@EJAZJcby^FSg0?blI8?koiQv41yV+TNq+W+{}M{88#L=-6IoOfbMlR-
z-vu_82d#Bg(ZIPvFgT4E3EdFar6BXug0-3<Z&w2ncwR{@REq(#jTnq>Kov(I^&O_B
z^-UBvKJm5!w^z(PC#Pf#`W}(+E2+>uAwhD1x;W?a0r+5O6Tt{0fTPQYx63A8iilHN
z$_yCVxXGRZFF0qO?QSlaxP^J~0#ufXxWtMRc<cSRVj9jVs1yCRCAm9O@Je1N*9Q1k
zLT+G%w!9&_*)UO&GOU>x7}se$UbBJ}u4-$XWbYp?6P%)PjC$%@CiaH#vFf>3S2pq<
zu7>-H)hC$I{bSe&Rg9W(RgNg<cS~p%h<<ZzCjVb;m+cL!D#ojKzpm>$QmPX?ZmN3$
zENsR0=GZAkb>=hP6ldxE$9cn0+V;^*n!sA~s~!mDqzraNH%L}Enya(iVOJ<_%baRy
z%TU_R%gHLTJKEP4^#M$Ny8YmgS3;z3X<UKQ`4#TI2qv9sW3gM|C$Zg`5)axuF?Ky(
zv>okyQ;6udu72&{>+@zi3%8(>R^D8=q%I83t}d*K2|7{!(=0BoH5tRJB#g^fM+#~S
zYv}GF?E&46o|g?>ou|Afqu!*=vwibp)=%`cSz|j>H(O6NhBM%ADDPDH$D}mhRHO@&
zq=&GJ9Z9@#ic@Oz7F!ssU77Wt88nKb;1XxWrEE*C>Lr`@!Q6=UsG+=(64VhJQt{pD
z@Cv?P+g)v&me75yB2q{i?rfDh#V2KanB&Is<p|$MA7))PQV<0fNq?rns_Xy68Qbd5
zqI;gAd!DC!j5CKKmCpUa9%D1PuXyf&)M<fyQS_fDWNXt%Zn<B?fyS>pUH>mQ)IX!=
z|3zKPQnPf$Swh=d!PW@pk-+`-O(6u7fslGt5*F&atRM=vp8z9~?EbKf*6=)G40E=E
z7zueELT+b2$t-YDsw)AanG}v@B}XA#j7wGZlFy}>514PRF+r5kMEyS5FHOGZZRV5g
zh8xb`e2+7qUJm%ZqVL{V?e^+}uEJhwgU`Y|g#Dp;5Qa!bhM~k5{#Zh=hD>hq9<LO`
zMNPuYMEjwTT7yQg1$Oy%#`3X!&jq)G?U;cv@n*&AcBmtRyREPU76S;k18nb*KhW>&
zP-8;h<}iH%Mhx7v_apbiD6%8>DbKPI?;0n4$wV7xL~pf0$w}OF^SsAvxvkkPR|{14
zWGC3rRE@|YDM_r`%(0#+8^dI#sop@6OYAhP4>b%)cO0@nvU%CZ7(X8Uy#q5FB`52v
zA99oE-2tnVPR82wIn$n^(`*Z3yan<MU610kPb(P?KKYE3x2<y><_~t}ibVLJ4RGW+
z_{IJSCqb&dn^Z0~@~39##<P@f0PBb?wu!oTJm?tm6ejJ|Ad`Z1O?nT72nAO><=M9_
z+=gx@L(XG)bBD>tCmch?5I50rhuK>iEVZQcO2u!`Z@C8y0oJGyWUMP+_sOTDv|B_?
zX_P(dE9$x#ed`(2KICP?Hw~PrwDB>Se!F<gvbRC6pgc$na2t5|zdDStI1YV#E;Z~B
zl@*9WPP;*8I!-iCpch`&Js}$ifJUEj_6}ZxQFH5**E(tkP;Q_LZWB?s5KN?ljaf1V
zU_drES4;Zb;AAi~Ae*n%Cb`1)9L`-#4}m+|f1MwUJxGtpJu@o-7iz5Pw;UQz>D_s-
z_V5}EBVW7JH)|Q`Kd1g_op9VO;qn1sI9v6p;^EXPh}DE@*;Pc#tX3YdZB`c`(9fxP
zQ{d4Xw)7`O1+A&nUuK@2y>RNz-NAH@d;Dq@bCRBDMW{J*!?QhgQySFD_r$rO2=asm
zoLsyVmHGz%WY(-QaB7$3`5+5$b?yvN;@Td8Kzdglxw-YkBiKja`V+c<<W+be_u>dj
zaFL4i!}#(<pFwZEm|j*Khivgz^uiX_q^O<h2ssBv-%qD^w3D<pkZB>+Ji_Mhy<9s*
zdnGT@BPTC{BRhjfel+cdl=ulUQLM`94Ms>%4nE_!=BU-(cDMmk9>seZAcxug$;A*N
z3$)#4w!!iBBPk~`zBNR(!27}+_)KI|qU+NHCi@$EKIgJ*oUG_&<(<2Or8nSI!50Zr
zvQ@(eB~w)Ji;`;o6L6arXds!?VU8#2b4^m+Z7a7UX_zD#tPwn>?6-we+V@cFMqj3z
z#S~Q9<SQW#srbX?N&rijB9jf@&#~cvOO=*)#WgAISu7>P4(W5WsdIZfe1{tTZI`oH
z2uqy$8(?m|KcP{_$L*F0aB6b_k<ir?5@PvRuI?o4w*pi;f!-6JfGr01yS#3LJV0I5
zc~M^i-`dhUQyBfSgJ&ep5YYpsj+tyxSzo7$p36^Y>FT@Uh|SJ#TM*~jxXvv{?*pPW
z1T#V#-)FCTAkBVFwxz;p!qjR2KYr}kCVQy0=r~{>!NNoj5<Qfb)<z?Y;qwX2n_1hY
zbinCtXj!`tsA%dsBqcv<7afU=o;Zbmdan+zZLDoXpbM{Vr)vHHNoscor!Bk23Q~l!
z2G^uQ$`;^AGRqRMJclJFn5zflrqJRfi1dOe(mmS=waq8E=h&AKr1r4ALn<a+)H*@r
zO?<<oHKySXS<PcMgAw(>nmC}Q7*}@(#GqLS8CRwcr~fh@EU8O}CLP}$Fm7mlcVb}v
zgH60nX_j||w~(qn|B2@KC!~EsotK!;)ZTcB!3XV!Qc+S|t_qeK#7+-U`*p)9WFEI&
zvq>Sq1bqeeZb37+N>h*<Mz?%nm9EPYjZetQvXyAqIWi8?@!PjVi&$Yd^p%**x0@1`
z9+p#@1Msln&t(l&<GhRv;8I%u0yid10QQF}9-@hw&Y#v^oYbBwP0~iT2vo;TpYM5D
z)wP1<T;WTJUE=XVTBG@}ZH}p35TltWMpF{^$3m)4JXN3mSugA^d(@8=9N@|jI|!Ec
zDeZWH7QoM?2cxA4s|C7!u^w4GD8-itHMuC4TN1?p4oBW=1Nun%v9u0GNSRi}BLzj7
zJ$xS(j!Kt3T?2FdPG5Zl0Al`$N^8tdRu6bgOs|d{pJ#;v1?J7}1fORJ?L%D!v}}Nq
z@urst6mdue(D9}`e(5T~pm@;-I=o7E)c%Gv(2HXnH+E7_&41ikwjlgslY9dLQUw75
z;`pDPoqxNt{9W4CP~S?}{3{Y-Zs7cX0Xs>GT2eS)biiqottF=l`=;h~tw#4y%Zv4W
zEhzzHP@=|QbOJ95>3Aa9_BFT!nTSuxKKa|cAH4)BJj{UkQbe!SG{@g`j;j+r7`{NA
zLlvBpdR34Jax#yTxHI0Jj|yZj)~us3$~g=>r{Ouosv4a&$ge(|<<v5__lB&cmxyfG
zY1e~zwCiD!zUFf`Qh2^duXo#egq~Bv7Q^lY%H{VlL0#vMcM@}BRu8ee%k|CQX;_r0
ze!zLkl=VP?Bln9gBah|HjvpT6TSg+ITh~lKHCezrCKUzk63P@+lLi#I8@ZM@jng9g
zXsyP*ev)=%D>^?MUx)LIXt83E|7^&!8N2wNiMYnr3M9e<iKxfhi)-?Ap!%y$z|GY+
z=5kn5LU=vw2YW|-5(>0R!}vW5TjfK}-rWx+els4suRtz;nGwx8ye%@qYv#!%H*TvJ
zy_gn>{3DL+qeqb7qY3D9<OMuBO-P!;<f8E+0u7Ns7`3s9Ar1O#U{qwXAxVKvBfh?I
zCEA0G*NpH9MdlxpXDbD*{#_3}X>m6%^3^UVzBZ7{-7sFYHQJY}!7Pk-<aXeH_xR!s
zC2_DIfq<kCfPjep=aum96wF|?Fke(tEFY7G4rXpxPIP2-ir8W}t7~HD_{PQWu`U}d
zeKb%y^7i%IYcyMvR;DIU%GO=wzigWDRSI>{v|^H*tte*qRg`%&t5mh#G2Ss2PcpVH
zkyIz?U!$LYZy$K(oOqs0B>Cp}g7vz*D;XOG*Me})ZPH_F86QyCsT|r%59dRJji_Yy
zf>7}VAw(RL7|aAx;rELfdr1$EsIVNMP^I5WpdlP68N4n)<12i*ZK^CeN_XyF0z(*g
zq;ovj`Bx*TUK&Gcx2=&iR4?h_Q!gGs+cUx)0k)-Xz&px!w3*7aO48l5k(tQxO3>NL
zw|HPXD!05~J8HKeU~*$GdEpB$agim)JR}Dr$b<qBX~p_6Ce9;_xKMa^DAATQmn~J9
z`W;vUd#5j9d!5|2sWxO~9m%wL_MeM~cuK$b???(<W#u+$1;QoNc`eeSD>AOX;FUOV
z#F9`IqQxV_PcnF6Bk)%RW?BtFddrd}aGGlzHfv}8ja4P}!@c=cC#Uv$n_9T>vxDxc
z9FywDLEU=dkTC>HF=rqeTha)FI^2fEZM51=*2A@UE>8BA!;`I^-y?-<4h}ROS0vj_
zzQ2f7p>DB9nMxZH_b^zlLOiZjV#FpbHZZqmAw9&;-b|aOj>!DNy=g+9sw^tOe1?;l
z$ebvAXs%=G+lIo<?{A2wq7+|6rUf^}zV3Zgu0*vM#>Sj5f}@w#kSnqp$h#R{uM#FQ
z=CB#2S+l8JO3J<F&Sr5in2WnZfts4IF)X%REzUOPap&&nzvW=Xd5G3WD2YNooqV_Q
zv61z=;O6O%oqX?4B<i;vUsKf-X16#BR!}^q(`>r04r+GbNpNRRaZU3O`kCwe!*S4U
zWyOtjLYjm!;0XRF;G)X-BUPgcpNwP7OVu^%?1+N2GRkGCAWqV6{83>DfHfcuRb4|R
zD=9^rR0O?2QzAYad!!5#a&^cPHB{A^#UfnpE|!cnOMhv$8etFDa=?oXV9eK7W^pyl
z49jxe()N{=7Xqa}D8cptn37($!B^S*`E*rl^^zUNf}1%2!=ks~hzJ0XW-c&9L1EFG
zbHu<Lo;b*g@$g3}v$72qrVB1f_4kR)b<!N5!KK_f!cpFymUD>ZgakzXG<zZSDcW0c
zQyDgf>*=#KewQ6Ud)FNx5N06gB}n@nxf`A}(<Ul8oWs%ySRt*X9N^U;s}34<Kwrmj
zF6ol2`zb8MTK01|hxHxIcJ!s<%es?+^BuXs{zHU(-M8sm3EHljp44kaC5u6YyyoyG
zgW!}@fL(Xp*Il=Ob?UTUkTo6qyrIH+2v;F*=kP>vMsq+5XL~?}Rs+JIU)F$KL1WvF
z&)rO4GX0>H)Lw2D4r{<Q57645gM!j3aHcZsvRqVX*;XaKg;vpe<*Iq9EREi~W<7rN
zO2%+DROzc&J6W48e;x58?O(FKrku`5v2`yR1;e<OGPW9_t_n$pOOz}O^0TaKR;E<7
z(3W~!*Te~1uF*)Em3-?Qlg`(m)I?^hv{O);T|cj>O=V<P8vbxczlWyVINw$L#91B%
zRZ$Uxx+rH-)?!;(L~20a(8WU#Mu>45vY>F(>uzo2N^1cFo_JW7)JmLxKaWJI(4Ia&
z@5-55hANPh{^VpVP~{bc*86jEk`0Tflt5=&ri!1na@8tcnZ{xv&U<-<PT(xs(2Tzc
zYdSaGdYCcUIA}oZ2i|Q%A!kB6Qre}V8yjgbU60{}ukV^gf{XuC1M_@~d2{snz@>X@
zI}C_Tj~$#7f4^fkM2oYh%Ay-W|IT}lgNBb$_s99KZ`W%P-c}<Vj%_P5KHksBh1b~)
zx6aSmB0{b^8ZVU4nRk_2eJ&sYR_ylACPB4eQD!69qK)u)tqIgyBHW>SbT?J)+Yj{a
zBuM@<IANIB9LFQosC}3x1}p1!x44HBW}QH!M6^Us@D@X!B(C4Gyc}$cR!UD4sLzvl
zBwHa+HfgP79a~m+7PWfIfg1z2c)O(3yXLjOGKdbFCux@S<%tNhK@ykG+AMorsre?E
zhCfIlII?wJ$UY({b*7Rk=7U%;PbqWl8Qi|fgu$4}&RR|&_oKw=eX|4?5nX{FNAhiD
zCB~RVzr(1tVVJG2dd+7l4RR@q;}@pX^ENOYef~~1L!{UXAfJ8XVFTDw=q-R*$p*F1
z<b5b+tYdqq?-VtcS=t{6p57^$UHKq&-^ze(dd;F>cPw^?&d*{~<vu+6W<RO6px4qO
zx5APlQxLAZVr_YYIb{vY1ZsXbYgCFgN}0MX0?FP71O3r?d@~>>AjBE;mF8n&_H)%o
zb1@)si9@*|lk8|?8l6LIv%`to83@{r26fL%KzWXXgKv8wTzi33dZp0kkTBs9vEdP7
z$sq-zmp|Y!u2FOeu3w?>46JW~0szs9#Z=s4n%Kp@N5;=Q&&Np9-<0GCfx2aOATWm`
z9);ZUjjMc9#X7DqPOVASd)lMr6N-+7$8&=sFsk&)zLJ?F9_vmA_1aBTrxDMsT}c!F
zbZgB}?k3+Ha3S8DaG52zM$sYMq;J=A<9Bj+Nj5n$wvuC!uOQ04eI7*d7bnby6b-lw
z!i_tCDq_zzgMX?cvfe+rjXoJKySc@kp1tdm767K@+4jJ9hCygc8o4|m5)Lm8_fLYy
z-95)u>XYMjUf3S8(g!~7ilD5v1(5uxirl661a4#%E)`AP=0ne}OwlDk@8pmcq?L?_
znY{r@M|)cr=w15!4_!fy6+|Wfr6**+UVnk>|B1w>b^Rx+k@g>?db=+y4xQcqVYw?!
zPsveENvMcR$V^O5(2Pya94Nw5%Bb8<&?_;ps4>*mGBGqWv8dX)z$q{=F|Zs_QH;+>
z%Ft0Z%g{?ok4Y@aOphH>QB6^gP0BPK0soCApz+1~*0UR$o&NeD*k7{u>pT<4z}ei&
zh}OZ_(AeD0>2IHcRZhTWfDp0sA&JTi!_bhR^-#5JoK&F(4G<+{)C+9&>(x2k$d;MA
zftkD7$EAcdGSOK&ZuNzbj!(Xfj7AfvIkc4Dpa0jl-wYXox4#z76kMW|gb-8%66VLN
zRi>YS^3lriJk1CB;dIw%ldxq7ugrw_j1NrqDdi?p99g8=ippzD=mxJ%n0FE*<P=EM
zaDDh|m1PkgQerM{;J1GR3<vgR!v=)o>RYVF&Saa<O@Sw#eqm|B9DM>WrR!+NS<jG&
zDT%}Ib>2<XGW-5tvG><+@b$I-&!mMfz4woK2LGB>U}$CjZ^H-vdVB2XV=~VdY)k12
zw#E40-!5SAS3=y-NyygP`U^`aZEj=y#o%=L0`vb@Ry;{r=ZnIR$_oM$LERi`U9~fd
za8%&5!Ivf|4moEgSdKJ5IAV;oVy%|~D^%dyzmreTGB-%D8^g4%5i6hE_^5xf&FOj5
z=6Jll^~2Zq54c?@2?*5X=_)H?U-UP;nWSeycTPNaJTN>FfZNb7Z4Kkl<NFd=&8-_d
zz!Ko%E<wNMD~oC23MIkrwQ-)U*{|{&s&s7T+AnYMHrSA>|J<p~+*PMx8DCs~t^WGi
zBY8!^$a#nRM9@LOWVLnjSJP+C%_*61l4^xEDccr43G7JQaGJACn$CpdWD9D(`ULZ8
z#}ihIF(&~)Jc1N=zP>p$pju+yl5UBYW;E)VmT@;g-%_`CvTp~15w+_yF$t@ZGWdei
zznQ|^h(Xu8&i!Y5H~Vbeh+V}QS2k%#K=q}bIa~fXgcAW()j@5z1!lXTbSaGJ0s(YJ
zkBbUHgW50apGy+`z1Dz9=anR3sNWK)9OC=<@L1vOsfd8ZPBOq1sc`Vbr0`M@^QFC<
zY=$6Q9@6_Emw=Fq<I0TR+q^;FBtxe*N8_t|JYfT~kcjIaGG!{<ro~4~jW_D={DP-D
zNJLUod8`s0*czg2s;xl31!PfO&7vUmu&Eo#M!u<wgkNq+iKo*POjjvn=l>Q}CFyvD
ziHpGq%|?|ZF$-3-4_e)Zyt`R|rZy6AhpJrgAt_bHJdvfMw9VSC?d_103(8k=^9xC=
zn2v+uU?V@l9Qlzx{G<4V{LnIpXQ+YS1CU*pGNh{4@^{GAUrUkcr*Ta2*AhJadi_U6
z_pg=vw<RdA@9>4O|1VoMI8xxRVHhHxW=)zfE=TR5mO?Y~sGuT!D1$DgwC2DPzwGFE
z{6dhGwA=0Y9)1v*Y+ro10gobvKne*5om$y6?^;)-){e?-Na|c$PLZ4AZ8($M#DBG;
zf>>%7=e=7?i9{wrMRiG~l{9D(f=l}+?ny}ep{|+@Q%O$IctmG$f)YAm2St1NB!!>i
z66c&CUZ$sDJHU}%;Fz8{Z&B}Xzi89E{-aVU3PRzPer>zwF9VwAzi+$${#|9v9UaYW
zOy%tUYRm;3Ouz1HI2r#dh>@ftx1fhQ@;SrSUUgxVOW4hXk=R9O80zOo7C_G^%hIP8
zpoUPH#=506kG@0Sc{hynL8vR9Pjc6fup7dZT|(=KBQ=~%cEu_8#Rgqz{09dwHwX=l
z@la(@AvA%7bg6<0IW^f-gmOxl5od@Md}!SnPD+?@gyC^DA;)fG?g}oYgDdcfA972V
zx$WQ-en5MzzGA3+in>^LlQC*PU8TZaff7gX`GYK)KbdkooCx+AeL%38fZry8sNO_{
zB1gcywOegt`KrWgqcCBwzG+}e#s)_b0iNU<CU73l-H0QasGRS<SqH9M^D<jROpc0F
z<-oSZdERQpS4^S)DCHfEK3!Wm0;gKWW}=N2z|c~`)I&m#6`yP-VCBs3<oC4l5k$I7
zYmZ4a$F#E~+vbX}fK6x|MwZZ#FW6-2n4zF;3N-N04cOi)IiwXmor>&C!X%46=+CuL
z_k4=>nTd^H@)$JHd=eTURJ>=a5g3AkgTY*=4aLEp-s=RAMv4hkzW#X%)h|y$XP$i8
z<_AbWyD&)O8*=y*7y=wyh057)b(B){P3n2+BhQ-*YKmb}h_<G%%-{Tmh}6v0Wdpy1
z5pfc6VG&>PnG<=C-CqLbdF_$+|1JcK;u_(T&A4Lf-Q?9Ha7dUIbaTqafy%|Ji>{bQ
zg3T5Il^?=53wN`+K_4pmTJ2N7MF%i*T-cjQ0Z$8s%V80wefrzsfdrRvCEi%K%Hh*p
z;jr0_0g;m?6(WvdFyQ;yf<q0HraDA63;kd}!nEZ-h{9cHMa@O$;pJC|p83-vmvnsz
zV{lm!vfw2|kOC~DPwclzG*P=#3sU>hY^2MsvbtG2p?gr|1eTyHqA|lP#tMQZY{o%q
zhV+U;tq*#Sq1rDp&-^G%_4Kzy{$IPjvr>lXFX(gN*Mo=mf3pMrx%8F3+$kH=ul-@C
z|3!iRuTd~r$}?XOXw*L@?Z*1ndG`f+O#zyD@<x<|UkQ`KFsM2t6xgus;;Uj<4VSU&
z<d>S>u?z^$JK!*~;RfHtyD66{K}!n`r|pdAZugrF+wI4Z8TuU{)Vs-E#A7&2i@Ca7
z!#O_yX;Ug!vP!y@!bo_u3c#QCY*a6UC^_ec-t4%|mH}p&_=v|6PqpSYjzSY9lcv38
zKObVY@^6Y#u#kCE@tZ8rdFLk(Ij*RBPcLE~q*i<(gB5$48Y`Hc7RVM2`!2xRt?*X!
zWIC-(KR!%Jb$S<iwyk^Hc9JBl2koZ9LYvo-cqa4$y6k`ziPToys}9!zU@1J@8zVPC
zmk8-yXaxpxWcy>gQrogojkn%Hpo}{o`|f!{%~O~&iKu7=6EXSX_v=u6d(h@55EkIc
zcmPR2DXjh%efbps`Y{1%OMCEYvEp#|#-qhDOqmVq){>0`o+P2a5wKicwPE0%`_E|O
zJ*|#tTh4CS!?Gn_`~`2s@`D0UvRD&wos+3r`vLQI#Y$TA0XO<4O#G)FsC3K?2kbum
z7l0YI$y-(*6R%*P=sr5H2AnpOREPe0nvuCC;?GvlPigoO$SM%3Q6=%X95WC*oCA4B
zk$d5gA4H<|dL?#W_XfVTKOt#adEVV@wI}liGUjEGOlz$<9%U0%H+2hoPS7FIBYNa6
znS15@5$Y9VoE(WgI!HZ28GVpIeMMDuC;Q&GY=e!Yxc$9YFrK?r2%p@cVsw{oZh|<(
z^U%Buk2pIjw5L!y(#j$47VnMtgN|YK{Ph>JwO)xvJcVq?RD5EFDg!I4!k$b$Pkh3!
zWace!C+Oiaz36*J9{9V4R2XY~LnCHX0>N(uhrEOli-cQx-{fLn!Tw#7osz)ai+yQQ
zsjv0^KZvZqRr{~X?ri-v=jdw+hVeg2{Xa{5Qrwgz2qS8++#7R2Q?sTu^?CnwqYA`I
zQb1?`Lu+l(Zzt^HxHMrV=5xz7s@B_YUKETHQfRaR8k>jNo4>bE?*H7qf$zdjAw#b+
z>2HW^i3Ij?o&=Qe2E66@$`O=dC?I)!DBB~s!~j5pr8rGMRdQflGKNTs!JSaei;gG!
zM1{&*$4(50vTlQR2UDmX1w}@X9s)u=Q<$R5FmXnMp&L@e9?v=T5GYgfrFMsDB+Sgp
zdbAN6Q`~+R!;(qzrM)iOG3ILIOZ+NA11;*FRPL>AyW4o1SS|aOs1Rf*7@YeMqlR?p
zGu1O8JZ{+BDh%WP0b_E+KM9Jbll99fd!b|DT3SH&@*_N&w1Um~2wwG98Dqu#TY3=(
zg`o~JYz33))+v^ISI5=#9co)nE$>#Ntv*CUJ=kQ{z+_oCoTdGO%L?D$4D!D!&<_l=
z&yg>vXUG?S?0;2(q-?&%aQ(9k{ZoPb?IFCSZs~@4gyz#@l%PQd0tg23S1n18HF8Aw
z7BSmMlwSzo$B~X~T-SG%24`p9NLvHF9Fo+!+R~k6Q}wKaT=(#oL>BB^)UD0`wVW^a
z`Q-k_<NdaiQ^jj);?j_@RU7u>_esV>*3D+O<L~L~q0emJZ~WZbNp)bZ)w!fZ1t^Bd
z(pW_5krMa!?z&dGxVqZKauS#bhJyV_SS!1wuwbr?kHHMx>i+b56VgsC()&IA8=l|2
zRE9mgcq2WHtp>cAzHmABV=YEsAw>QD8^9g!@a9kdn*)f>clrL9mlqC#AA|>(_=&fM
zSkATmFHpYZ?(P(kIrq={STVb%(!U>|-+`Z{UPvq4mHTVH+;aKKLQC~yKTDkB(4d5%
zwUppI>l*WO7db&k+$|)ttA@+CWhAW#^2cTMezU8(;b9i$ZPU~@pD7oo4zdj25dO9r
zf!*^jC;^YK4pN2~y561gU;}1Y(WE`AT8dqGt2YWbCef_o<}!^3o3pl5;Y)IocSw_c
z73&dp*fQUM{h8H}AESYV%{W;fD@?TO+wUNg=tl78YXkf1(!Iq-oj=FjVA5R+3~(({
zyMo^K_r{`mttS!O9`4gtn5dAy)a~z=(WK{du(=C2AR2|B`i0*rQ<E_ltpcCFF%51b
ztGgzc%cORze0kQon|}S3hgxe)9amowESq-Vj`N3srP@tpC`&B&I4^tPiq+@ztEUSw
zZAIEy)gCLn3Q;H5nBz$;UCkZ43RkRsX)>oL$+VPuU{cYj-u24POJ`>-N80I1=n1@~
zD{lcTDhc|=Gi%-YBN4t)M5q`T{@tBVgforI_ARNscoOT*#)tX733Od9_qB0vJtzYT
zJrLFL=reZA%;SrDGffUN=%-w@KDgGnJaW+G$&mVu=B9#8)0fi~XqP;(K2<9<J3JSP
z)vy@KQ^A%10az#&IvXXf1Q&v^Gl7>D(2*%9F0~KUs^ujvXN%2wyro9%rKRgdl_})~
ztR2A*d%lBbJg&--(fXZT3AP(bFu4OP|E#Uz@)**s0xULXLC3{H>nI$1-s)}rH47`G
zC0b-zYL37is#Y*|YBzKt=xGD-{>0wqe7YS=4tV3F98RaBTwi6h!G@`Y?@c>zgaE44
zK2E3x=xIaQK1Vmv{s(B?-Uw))k-pRQ49QmTB@5B^0@>5%ZlhQFy=UY<xw|o!fj{;H
zqi;U$6>gb2K4#oR`b0XZYJG@?suSZvFW7#qf)uou@3OuJr>uan6)s^#W3MDM<CEkJ
zE}0N}fYgY)DfMa5yj#Dk^zVQ!8YGqPB5xzLK7l-0_(zjAN8=VRf!R$>oC^TUOqdfs
z7GuzR`?v3RF`2O}uA9@ASYa-dF4)rFisD^rsXISfp}BdKLwDKI@Z@EXT$h$qr0Aw>
zVgY6~dAj|$s{p!Lki4Q>VL_~M?lGrz$*`vE7_Y`C13e|ZeWV;DpLe?DAk>ZO(yp25
zPt$A`YrIm+NmxM8ZVr1z_qur4Fk|LtC6CR((e!^BkD|aLczcz*qwO)yHal6snW7EY
zFe4k|-T?1;E{0^KXM9O$o3%)hh>vDR45m@&ab7gWL#4;Jn?m3j+osmuLOzQ2qx*NS
zd&S{aZG2_|Mjx>z8M->hOr{12Vb7}3&rhVVr2d!#7F%*mn?xBmBY`c+(&uDFK8}5T
zc5x={hTlH&(KzJ@T&kQJG?aQ#9kk<Jq44KNvDnvDsW^@7hN`FUKK-V*wbB9!(X|K~
zhMDVXx-!M)%h(~4eBxc^<+dG!-3kdQ`jvv8i~9P1&s*(zn_uU?-gpE^)ppd!hoHqY
z1es!Ry?(#K7pBzrD>geomCQE)FPh^F0+`CP1>c)3pca}vp!Uz5&!VadS3ptc+<o5`
zzDSBS8|3+>!4pn(hshS6&?=cJSmlNrwGZ`(Btt%~&i7<Muq$BY8U|5!RD?g+9KsV?
zMNe|^bfAQVLmJclgDWaLq}CjM+|J1O)V&g$E-E{i#SUI=GWULH*t}AfK<qvSiq1B3
zC><F#?u*yP+_E}~k0>XAESK=Bq)sDKAjJ*6YroPp(7F3*67KEJ95&V(7{bm`?*LIl
z?tslgLdp$WC85;ByFs0tAuP#J{m10=mK~ScFRJctz48b7A;)a4Aq>>gFTM!4Bo|Ln
zuGx!Z_T(s~X{Q@y#E;Cq9#ZQ<H$PknS#;yeQHe!kjZ@F6_S_ZK+dsH$Z<_4CUabi7
z=bW(#p8RHlWeaV6jJbf-(4u>3wop7XOzx0WwM2TC0!(Tw-*7TY6dcb#d|wSL=yL;e
z;~iIdLbxPH-oyu3@@cHGqvFU2Oaudp1v+W}bUUM4Ye2IVqBTB0$b?oOEdKudE}xCi
z?~Do_3ZqV<TU^;CUgwL}60yQSq&s4N2TnGW;ildhoUm`Ljhu3u?FR9QbfZY~=bE!K
zV$(O%!6mgmAN5%e#b!t#{sSLuk69P+8AvrW688|;>okY?qOl3C097tV3t1;q{~g|-
zv97F*OIe2v#NI?8mEf-N`tFQLqih#(spU3TU0xr0Um%!rHV|6LE<{ZqHcD^t0s>I7
z$G~?^s=?X3hglsQ8ME+=Li7jc64Tfl%BF;#YCv6Xr^f%&oEtDAc>i}g>0b%~*Al&M
z>Pxe2e0AyU|N9>0Kf9A86>GT#c~l>pl{p12Ok1T}Fkx66TVp>$Sfoq?bPR?xqB}#Z
zDVj6Qd6&VQJ%qi8pS|J+FPAaI*&A$*wUT>6o-@2BoHK3DosGWLJ|E!nz0r{@$3~C$
zY6xEmyHUhC3PY#~WL-)1xVZh_sJasB2e-~pB2YNUy39`2n8|zAOy3<|y}|2@2+m=>
z(FW#SGonDYUMulZ3$aR9S*JEva073IicY;*wE>gcb*8JTV>Y;!(Z_4>fr`yETa;<(
z7w19h<*TwfRov@&WfiOo@=zPIl|uN(3CFGN%<c&Vql8QEcwld_9l?aRG{YqK#`}%l
zZM_UgH?sls^Ex=-&)B87=H%%X7OFZ`9?|+s5&ltxN^NWh+kJ7m58+wZKG0v7QRnj6
zVOlUKW%w7TIt^-6_a(9$V58G&Y-g#5be@8wPrRlxtm3yCSe!eJ_3svK7VKH+-G;Z#
zqb@-?CyUTbr)sz0TFA{<syD010?8X0)Hv3=?@GwqR1|x7&PP*%j8;Pc8uM!1X6-Ep
zxEYQ)Zo`-%Le$7z-T^o5We%IfJiD8=R+qY52UsD6q5||7TNo-Go#y~TmeP+lFD)Ep
z=k55GO;SC5Oq&?nbf;oJI)Oj|^BI-=%Q-l;NwKJ*ra9=7YNZ5vL;r5uE*2i|M`u)`
z+vI{Rpetof^Xk}4zL48M&~Jf{*ZYz#!9?*%Kz{l>pgH_h3a9T`qw$T^lbJ;J-|k?z
z#qP=5>4>hTT}qdSU<zNlJ#B<}0=?G5mm@$i7%676K>K3aaT!7s+8!ZeJ`yT-p(HjR
zII|5Ry-H3E4!(z_S#xbQ+&U4A33iIh&1mPFCTBBKl*FmwBKTG5nM)&bJ`_pB2f@Bz
z1`k>`LhDbfJ5ASr%N0re-jBP6GWz?)eDdXj27i`Iz+GC2vOaMQqmej)I7;$Iy~3M7
znflP_&wuZ14h@#b_r7+B&6ke;Yi#dd({umkHvg@&`Il#>Vq^LLqoTW1CKSK=cwXMI
z@A^#qkcfA|p%i0?kb8N=p~OW+DyW*l^?EDcl@3mg0O`_+KW~3(i=)L*Vfgrc?hdc?
zN8zxy*LL!zK_dy0+V352UvE5Tc6(n>9e&)j+yEWgYYD&}Kyxw72~=b<B=jE+^(6+F
zPYT<!hFiiqtQ}<I58(>EBe_nrcaQ<Yo`Y;GZy86VOn9W}8w}Y;W$xYQ7o9*Sr)jxJ
zlG<RXNo&>Yi=OX3z!F_!!;U4HzJtwZY_d66W?Qa<;|;-tr~~#2Ci&#*qH_r2z!uoL
zM8J8yGwV17Jcmq|vHB-u<H9e{DJzoZi?8{PGm`0a4^dm2rgfAq1NBxtE#k#N<StK|
zALe~pkJ6$)QZi{1?T}NmcZ^TBy>)dRq9i3+coo^R)^ckNmA;O{&o{Q?V?CL&%v<Eb
z4GHGA&2w!wA{liLNF2tu>zx=4*fD!=&wsGgy1Q)K);ID}O6#Ul>K9{)YJOXoSk4UB
zn!QQO|0px$MA?s`@|Yu^><qX0uKY+?CMb{+t`93(m%C4fUeG$k@I157$FPLD0kpV4
zm~1U(^tQ)_5w6d&Wx)(chx;Z(PLF<L<4O1PL^b&7BWk$m+fp4hqcA}~{F^!;78Nc}
zMX_=8d6D!UNj%@1rS7AVdX7%I`==p9Y-1g7P~#<j|N0C5As$z3EOADFH1i)Mrr)5!
z-lFWk1NffMN=~IKkoAB}fqY5njQ&V!Q@&CwNQdT|+S9diN$DC%?5#9;5)=1#e~mNz
z#j<P65z_~h1`}J~95b3hvU}sK*9P9rKd#*8cvzIs`@_qwJYC*1E;8azL@MTupGlm<
zAR3f)%Hq0+8G(YsJy8L%`l{UET1|UAvWn`tI6WPNHclboSwt#ZGTslM4Nk=+2!bC<
zQ)Mb>14;ZRn4%zAhT^}uB2<QNB7+!nXd`6r2T;Qd(Jj>94X5{6RNTXOkPWO%?0GBS
z|DyvX_88NQ|MI`m;DG*fti*pf-v8`6|K<0pYB?^bBK}FloTw8)h8^6)C-gH!*+d6U
zER=_A;TQKSln}}lsZ$YCj?>mSp8wtT0n_<dbyS4VsMKFL>GU=zWco(_CbaG&rfRfS
zFIAinHl4kcy=Cj#!+THQ`}v0BmvU!~%gsP)$Q8{d?X1VWecB6a68u=~Hc4s+@&L6t
zNPFidHM{|}37|3*5ao-WJ}(Q1Wg+S!Da?1H8O|NC6QkIP>O(9iwO>X>kP?*`_Cz+S
zI6O_(NS<Z&5$mcrri!*XEM_rP7aU2{Wl<?Huta`Z?!hawfm@<EF2>bjj~F0#7gV@1
zFqUA>I)fm^Uq`IPW?ghhBg?>cCc(02QkWfg*K+L>7YA%9Y0OGCC#waIRa#^$N`_vT
zbl3A|t%|clwwq}s%~xL2NOEjKuGdGj<yWD@(x$LKZk9GR7N!T=Z!MB-joKj3A#E*Y
zB_J!F0Yx&3H7tP%;ozsnA!0Q_r&D0b$towKvv_KDqV!gET1=KU(>-M~S-6`woqhOx
z)0VGR8mY%(-KROf?9aw9tgOk4krI^iwrlPMnWc$A$D6EPQ(Yv|UCbO)0k#%L2%>1L
zQCK0iGEkt+d1@umr@$kg?$tbZ;Bk_TwUiP2y<o|t1k91<K>qLp=lgZ>lGjBUPE(V^
zke-Q_pzVyZQ$a2ixYN9ARztyAZ4GOlH1e|fK<zbHU2_Qp2fo&ani`}yJ(gzg_iiqI
zO@V$B_Lkd0%Eh0hQ>*f=1D5IaM(zGI0dissp93jUgOn8pCruCSxt?;P=drityM$Kr
zjp%#%nz}})VBB?5q8k2zUC>XVVxneL(AWFfk-wqw^`!fLzwisX8p;`YmF|*wO~Nm0
z$6OdUb1&s@KtJjC1jyZ%1%L>?!RAC<VLUSav=rRYBcN~6f}Y3J+dHvz#dH2?pxmeS
zqS?nz?6MO<r4r)huos=|s;xL#x$p9GQk~0_AT7JYBbpcbCm6CpyQ_KKtaBHFw#is+
zat`*rr(nlSDbj+@c}Nj-SjftXf*<BmK&~N|6*VNDiWKQoVV~ZDxjC<mf)H~=Kwo8F
zYS7i$+-H3>r>Y7mF*-CZ5xiq;RY9^oVnS{q%*1kXWe2v)!$U{l&KpI1fU#adt7SpA
z4C2(z^FvOi-4V$4c(tPpG~JtvhR-l5vU}`3k%f;KH?cOoakk(%kI=@ZQsWPS?+?t&
zyaJ8IaSha=60Ht)V0g&*1~|d3gAZY0lk}mm)%@lImj%9m-om!m;|?4V47{!jKT+Ff
zBk+&wGt>cLM?6K0nw`QCu(W`<-J7%zp0wi(HM`W8P-rm*(AIfbT+(~PM6_y^Df=Ha
zYLr>IDHf`n*FjTU->&K`V;v(X5`S;zs*qoF0;!ObEOr4BvJ)(|!5}#k(30FPPtaVK
zKzK6kb$%&Nc~#c0q+zS(ojuoOI}CJAmq0p3V(Tx&t%VL+<Tmr?3h`va=w06eOK@y0
zj=h=4!gsu+b8-eM%#7ve_OCNL)t~7cRO&uEQfSoC84l2_96A;@Osu}(?XO*T8(XDM
zk+Da(_EVfekKxa-{X*=DYVN8YV`^Rm$|fZ3QypF%q`UT53(=vO^>HzX#lqLmu0cB1
z_@gt-sZH*FY!}xfNDnq%;|?`s7vXZE4V*awM&G2=r|S|@&fKRf+k9G-O0{=M{Hp2f
z(ogNe?ilGH+Z}@I9wh!d22MBwda+}p=DFzDXSIbu^Civt{KVnmFh1|_IBjzZeGKn5
zUh20r_C5bt+9*n);Iz#`8g?(qZ?|D@e6SMwH4bmhgfoz87CfD{yV`9BC-O~Cqj|@C
zcQgqo^3K-gdkbU(6R-`6<?2$|>Rx1ef`p|yU=_*NRmJTgEgaLlv9|-Sq<L1g_CNIh
zu8A_Ov}a1b+}`5<9=!dJs{Suc^#8CFlN5FS3f}TIXf(LU$TX(!yGBxqQre*Y{7!7f
zj~W0S$MeD_0k|49Q8gha{VeDylnds20eLG3cV+vA1Y_=kKb_Tal0Cue`se-Q19lhj
zr<YOf*}zb7NPLJj;Id44K9kX?+|wGo1?d@7sGBmW73+~(aOXi*IBm#e^GG4X(){M2
z(6W0hNPq8XY*c;AJ_~Y-X>+z_vq@2qojCh@0L93ZdWb-&J3X=hd%OV2^>f?#8ec*C
zICmV^fLA5O;;|^~R2w{RBG&QtW0}e`nN$zjFGCT{z;1cKUjDk*;f_0^JsLP|K@s$Y
z67_UQzxLgsO46j4IF7=R()Y;Q+5sTI*16HuK>Jn@b0wqlpAeZS%{zYAp`u19%I*W)
zRVc^QubIHXduDGc45+#~?N>bEba1cE*eRIf31a#bGQ@499{qHeDZz%O#A>|lU_nNV
zCQrV7uM+-&Yl%kQpZ*A_C!(M)FOaQ`Y&3<fp>mXT`kX`gQy4Uk#xy>Hr;$TzJwp*B
z{o*`Ps&&c9RVD9m76{|Ur&jvYzv9Sg5Pism^`HL3>eq6!Nlp9`!y`!l<0Jha|G3fL
zWbwcJeYG`T95u8*q8p?0!gVSm(OYCH3mnPDoPh7MZe<Y%WHPuLl=c#Kn$Reg#B#Bw
ziF2c2T9Q(Ev%y70VCl);%n|;DEKp=WlG$JGrg3aLZ`BaHxtX!0mRlL=nZE)S&)g?o
zrfu_Iqpe4NfV?nl%jKc~V43|9Bhe2PlI<z|OFL=@3y23~bBDId$m)V7B((eL-M+G~
z8dy0}xoD!m8+Y2sIbSi38S*>eZp4uYrtZ|;I(pLC=oBL_PJbIrdoPjBpV0?scRng+
zon*2;tYI~K_3A!4Kwd(_7xtZT{0QA?gBfm8r(RM#-L&|&V=vJ`S0-!AG6fDs?#s<i
z1*2JI5$Ew->P^4qcN;bF<7SyEFyNM|HPeF<qJD~P8B4H+2h#P9CYqx`L?mlcm<K+P
zdf(bFm74`aIod0*LMyfBWjWA9M<Q{mG&hEutX``Y&An8bC>`|;X8{Gm#}?71OKJ8&
zNSdozN#V7GFNy3s7Bs<|O4dN0cHuO$^+iXW18ia>PA8WsGizC;cz@1|zD<jVN~eO!
zxXs*}lt-MfUO?T+2zrcXs!5qDOJP{rHVhqQk_8DS$UqT!*91Lr-QXHFo8M(9y1Y1n
zB*kt~ZAA_|PG7FMk%@B~(4Qs&ZIOAMkDEk2JrM%FJF(2yYF8`RbMX5<USEebMuHj}
zN|}lb?>B2uzdTv}lt)gKQdw9Yj_NNggUJs}AaG(*y^A$gD}}tSaA~uUI<?@kD=bfe
zP8YALREs`S4c46Jj9bgS1*Ud#nrQd`(@ayEq{(|V%4YdEm$3}CRMECzQxZprkHem9
zN#0TK492aWEV<q}(8AHxWrSYRNLqFHY${e;AEdn-A_GhS*^Gtz)&?j1^=$WmXhLKC
zeF|ucuU3)!Fj`w_Y?A=mSQP9N;3-HhUnhHP&@Pa=envlEf%N6#f4)BY^bM+L6>2i6
zDKTFL&0Sa-tkh^aZZd2)yNxD$`RF$X3%O&1(jh-<)zsJx&esF_oxx{!OWkRUK*13N
zf_$y<$z`K)UQejrCw8lVrH0lS=xXex*7r4G4D3fQ0;$pUF*|cJt19Z(Ew<!46t+Rp
zYvXt2zL{G`D2Oh5j?A;NL*^)qxQ-P!t*|ceUZoTI`dGBd-NXR2_W-pSGNnLQ5x#=m
zlozxR(V75CY3fv<*AG@z<9E?Ma>kO~mKQuR_pM0164WKdsfq%%W7e3EtZKQm?QBWg
zVTLPdfsWo&nBOA9bfc|bd>~b{&X5w+cl?T}LpDZoqD(aBIGb#R%z<{-raEQp^y2Gr
zDfSx&b%(dfSS^UGDm=zakpl+Tu<ybFSr;?e&EP`UGm+MCIB=G#q){9TzwMf_I^D@<
z16RQoG?l{`QD>Xwd>BUGrt*^~(iPPIzs|k_sIFw|n&9s4?gaPX?jg7)xVyW%yK9i(
z?i$>KySo#DLjwQJH<_0ik~eSaKUKHRy;Zl@YCGM1cJE%Bz&*mx{Gx=6*9yD7IBeeu
zw1*JV93eFiwv}>!9>boJazt@!e#=FccEdS`L7Q6MT8aSSvx+H&m#|ngIvxU)pJu>k
zqVk3P$9uxLAhy!)Ux-NWc*BW?y1iMv8T3zEV_U$eoN#iQG$7ab{dMDYkHNPc7-g#h
zbT`3cznQ%zmEN1Hl<87R&x?-ztYV(k6rp@r3nvC{N!%9bMhYv|lAEb5Djuo(U`>0&
zORQB@K7j4$Z{xT#)}egb>Xb3&&)Bv1g3{fQc^mI**_uF69N(xD)3zI9$OA)|R%I0q
z{ibKDKdd&n#QRq^=`R+<H*~^@0t+X?#6B}V_9v5Vazr9p#onSg$2;P8^=DKN3A~8h
z#9SATLK5kM8iHOs9m|v3l#oovb&nNv_f__n^=c-$rm!CiunU)J56GyoLn0!_W2EkB
zM11ltH-is{oa9;D;y6t`yjg|}vDr0*dfMRbyE$0eCvUis73<S{3?lL630e>m{E{4T
z>IxC!e&3!p@4%L|deYjQ>rnD}ekh}3&ua70jCbWBV`yIpmy>i~BoQsis0>ekyqnY`
z-N7*(Ro2!hm6iMzI;c?<M3h2k^`s~+6G@js*2q+zYBbi~;FPpr@P7X}s0qv*5^e3+
zEofGLkH9gKyDvAffJM_|+CVzmG1EPSpz4*0GzWB7h8S)?*D^>uip9tc<_2l{1JJxP
z!!fCj+$z|$#H*d1Lz#9k5D(BLg*l>?I(U<;!766_)=*=82S6iqmv};RiRXSOw0u()
zM0%WG(RQhtJcd-81`p+zYryQea(#DtxuH;cbKi`{opo*U4VFyXQTr=eu75zJu@F}a
zyYQZR;e}WC*wTKA)L9tE%rZUnHM;9l?AwaPS`-~0nF9HCehLg+3g(yZGK608QPr0k
zw;}FaB;O=s+b4tM=1Q>D3m27><>F`Ki1^4wh^|oLUsZpAL==rNxYAsU?qrgBc%uc3
zwk7~3(l5XOv?9dP3VLd}ZXg=3wVGn=!NOR1YBdC<E}c$YI2oHoM||t($UP-)n+Mgu
zhpLVz*|QLKTWa(G_v!kzmxbf~Ra{vKK{Hcjpl0wAm3)KdWl#_3w>{T=OGch<qC46Z
zm&k_Bi}VNj+tZUxN{@M}0#LUOWoe=IVWG(P#6n~ccIVIBD#HX=&D{ZuIvBvBj_X%L
z*K1oVQv*GFz|9)q8);%-_m?UDnZsm8tdv|AKWe}yFi>H4zSrHk$DN?qC=OdB^5HbO
zq?k8C+=+}9L9ifDPm(I+FTcKcq})O+W}Fw^qCGuG=UlsVEU-GGuK>EP(%?tOaWUVm
zN)V$8I>B{>TtGpB_7D{Z98&g|R%lMITt+79zz5eVw^W;BA5)j&8tKrJ6uy`r+0Vp8
zj8uQ)QeT*SWVdu4u%i6MhkH>xi-ztto665Sz_Qj%A&^AR)<IhJ>ek5EeQHiH&_BYo
z?5b0A<XUZi0k2GWKV^R`YM*RQ`gWNt9%U9h68StbAQG24=xxY{oi@I!*<db8;-FVP
zi~hP^k~0z4P#AdSW%Hr-Uks15sikfjXYioc>I&dgl~zAvE4Sf=zh5?Cz-ROtFoWy*
z=mj5P&Ec2f1fgEF#-YeybjpW~_4-n$U@R@gB0hZVgvT#)L<ky8p$5ec52gUU@WuNu
z6nCg$M$M>=OZ7t=a5@j6ski%*z^R!c!y)^oz^N%BG*BPzoG6N}p1B|~nXA-G0*sfS
z027SpuWkC5Cj7Bb7t*ma0E8nmu(UI=H*qxhy)RO}!XFN?%<i=7x-s>)YQ3R}^ey@K
zx`Yvux`L#pf>Spyz|##EEF6;n;XA$7dy`Uc-??9lVV%+9NW*}m2qnAijWz9!@i<-Y
zRamXPVEOzC1VoNW{D91dr!5{%(Z10%+j70g$0JZ*ij$f{H#mWY0`|O~J02ECYiGb|
zO5&|+f3B4R>6!yX7cWxg<O3xp0xxyehJeOx_15j=xl;C+0sZHAX3Lg1=8cffD}XTZ
zmUx8)%vw_X=jJ0<J+tGG9J(@}GTJU#f97c=KFCi=+9q%YaUU`Lxtsg6o7||!0t{e`
z%tvEJcf|9BI8W8GH4Aed)PQLt5sGSZup=;4_n;ph9*b~6M|Xk^_p;z$qKFyY=jn9n
z@{2dr--#-G%DtrL544wyz(bO*LoVyka=@lsx+i&S*7QJ<J9N2R#2#%Gk8i0{wA?Ps
z6!Vc41;RL)1C%PIMbbbQMFTvOvGwa3<6GS<_MXbRVv@j>D^o^u(Wu?|lW@GbxMtq&
z>rjY1h*a%ttk9!<31mNB_Muz^2KaJ^+zbisFj_c*kVBQv^xM|@CNhVFa+_RtySUzP
z+c{ZK5}0IBka!en6*KgR+>3YNJ!8@j=L^166-ciFNaaYnnHid}s+IRrkV>xW<soTy
zY~o(YIx*P5G$0MP<)%92rdlDrCx|Ya^(2%pd;*KDNx+~V^9e&%TkdVE6h`OXKF1Xg
z(G~vcOB)sct|WnS<ONI@C=zep1*W6WSCV+a2Tb-ZUXDR+p~zFuELz*5wZFes2ISXb
z7$E}z^#NqM$p1xe{b4LrgK<`zsne#p;E0O@A&mj@dqs>es22+)=>3^VSTP=y0WvVi
zSzI>OXHYsh1xWrw?lkq4d`a#*50#=aWf6&B`O>m8U5gTr6<+hRldDyYB{wU#C08?z
zgO2Nzlo4ro^6RawV;;APw%Ns_^Rqgq)vvcrD8ejZa=Vl8_g;5jMR*AY_bRZz(Iq{~
z5*%&kC~%#;n=A@Z+rrJTH__t4I-XtV)=rsaxi(2p>*v}#Q0xqpy?Z6N6~l#g{^8Zw
zp6{!d+sxa{>RZGa@SiaWqGn89MNQ3DOp)TBZ87iMfZj=?hDva0b;o&V(vlbr8ij^S
zU!AiU4^DZw*Y`2oLo<YJBS%O%6zg8wq}1+wff@*fzEgzuqu$_F;H?pstFv^(&+4|@
zR>(fr%s{uR>$|X5AG%=G&bEn=9d^!<t!otzMmK8}F1+Z4mU3u*ZHRu+ggtj?U&Wx=
zXXma-q0A{~nD}5^D9-to7h~U@s3Lk*1p95e*pYL7)Df#?^eHt`(_+7w@R^kRGG)h{
zse6&E3gni9CSlvvW~lciF!tNsE%Jpq@C<dA_m^yU&NZrgwiykd(YVrDXZlBTp}tP!
z*7TO~TE-1JM)*nh=$AqUl^JUdg!h(WP9-a4oWCPo#*6~*ws44vlw~&UBax42V3a9m
z)|(Br9)a%nJztllNeiZ0)6QtO<eqAj>R#%O(b93Vt;32?o9JuAYG8pE*z7G#IeJHJ
zjS0_;HUpu@7+h~;_TIu2+O5bULPOq-rNGADq!yc#4>O_{r=?!70-kvp7<{;NR(yI0
zd#;h$z<W{4aC#^@?A5?9W~XO_y%}RhmFd+YS_yVN%k(V$T6MGI$>L5D3&Wj?C9_ki
zlGgdV57ef0YAq$oX*R}a+5;@qx->1N+=F_G*bZ$AP^rs(?=<GaokXg-cT#G?j<blE
zmz52eRU4U*ElNzoQt60FiaAkBC66r(7|a)jUTOkof*DexE+`Q!Thx(`^!DIXg0N<8
z(2t<jt7gcTEGV{nqf#Q^ELQu)P_x$ZV)rnpW--bLQWYm=VuYt?2%*!SYN5hHY)K_+
zlI0&IFFVAbOR>S!VK;Ntt54JkLdw&Sj2aqeV8IQY^<;Ce;`?JO@akCA8wtBhNLKhu
z64Ud!MGsC<NhR+lnB$uFPMXVydP$}7TMwAw-$H$aHwVfXc4F5U^zOmmP0Eum^CqwE
z-Uwli?}GAT9L1W%o&q;OXml>KhG7Cf)%F<=CEZe?J<D$Q_BTWs?OGHUtRF^&=lp0g
zP+1$S_#iM#FAZm8iDEOC(#8nUz-6JW0)yhB&3QS?%+5p3T-?kZ7XuJ`49bg*SK~Ba
z)N!%<!VeDYgB*ie0*A1@RU0c1o(`<760S_trV)fQB5S^E$O7%uEToJnBVfm7?rwWe
zmQ9963ykfj+QT$aHW8}#ku!xPEHLP;YvH@Ge8w6$*~>z=g8CX1BugVS<h5XZ!pnW}
z+TKv|xB+~$Iv}gEA+*7muPk-S;Ci`5W-T*t^YbQw&4kl>0bBBSZOfKnOffF0*ojIp
z<&B`(?*j+;(}nO3IR^A?JZ&*qNSNaTZ8H%|eP(BLP7PC2Ed&4orW5OOrlRTW7Xzml
zw;Ekf$NS^9xNhpj5s~UYkXm?Xk)l?oCiG_rF1{#z#X;UPcpzSHdc?+nw5$+U6;D%b
z(22%Kqcq^$;8<55D%S^xTq`{q+^9@N!1U?nMPS&FzMfQu6KP!c@^s<Yz9RLZp<NAH
zm`obC9r@H{Y>q%SDj5IRX$lC<-tdrJV2$nTiCFzAe${O<&yhF9w3xiz0=)rNNc$&(
zf>z=7B5Cs7P?1qsJ-Kf%G3_x{EXD<zr?TnCEx=a<oR}iyzG%;&ogIgE7bGs|gpG4p
zjw3V<XLq-v5V*ePy#YdmU~|sFGV3R|+2r1*gmE%yU0SznpUe(AU@X|oiS!dVj_JdG
zYw#I=l?IlNB&#H)IOx?i?vc8hM?~KPY<ptGlyk?&teqpcN8B}_^x;T4KdWt!V82jk
zKfX`wj7qRnENzGAYV2E>A3n@G-))ZWfUk8yz_urJy!&kNRm9I~j~mFCOiC{wDzrDj
z0DNr_B4Gce-`TDZkOeFK6q8g?kBLi2Rp@Aff%q^Bhm3OC@cRkz>Gkg1%lVILIIwl%
z0F5RG1h2t$w>w_HGYp|uolclv3HkItce@X)(@*h$^L0g;y7#PCKsJ~|zz?zc5o}eE
zgAk*1ka@OeY^>MG5BvJ17{b+f<21^KICPL_^}~7kc4RIa0Z(@?l_e@#kKvGC980Bt
zl>4H9rBM_iI%>(u#NvtVgG9-;pL9v%7Zv&<Yr_bRw@=PwS#MmfX>NT>ZCIf&_@f81
z&FMka!A5vz4ddea-YvM_!<`FcN_p!gFff&}CM^ssFOdmHA65jbr~H104>4JpD9B=c
z3K43H79K~bLI8&;z1vYQb3wl$DuiCIAtdDZq}H-8F`;>#VkkO&NJ@vv=LS+7)58&K
z4rF>Z4fRkG6Em4oI0~^;i&WL+@V+o7u&t9`W@<x4Y<rlM;`3qfj;;#Ruc`VySJF5^
zgH;^J@D3R4VqG@On1Q(&Cx)0yA>e6YX-gYbhW@Is=Ty~-H3p(P6yT*N?h$X<Z{g(i
z1qTcA-Pjzom2iO9+He%wLg~_mCE+~dkXjW2!^t$^$M9l}2lo8HgsM4Kq`Is(SMD-=
zOs)$%#NZ%8<t{`Sot&+?_6Kzv@6x|ne?+}yNy^_?_t<?ec6V@q_lEvk>p^61S)>30
z)U{2kb$00|t`ACTe6HhkUVhEHMmgw1JQY|d$Fb1HTkFl0_SPwU#30R`{Agd-S<3S+
z?epJ1u>_Rtv&`Q0MWvi{Q~C$COTv3#Waj6g!OsF;DKgj?K-3zV)1Oek<#0xAT(<1P
zE?u#?%JyrXz*FWEO^WA2Tf4(Eyismy6WbbG(w7erQfm=drEs)Q$x%fYnA9RvWgIYl
zYL#J0pBO#aTwYe4n^h&w4U<&nNZceL-Jhzj{HO!-G;*?Zo?)mjn2f2`HaCLXXU9KV
z6NC=ipxUyd)fquohDcChy_#B1C`m>$#Y8dZuD;+B!c5~>wVc{k(0mE3h3k@4pdRPT
zhcB_RB896&uwd9Le+B!YVhK%Q0GZ_?u+&<kRV#oBHW~wpUARjEVwYxUn=LWSvz)DH
zWXv&?r?68N1V(AH$vTrHc`-MpeC$dfMk5&>?#nij4ZGg{h@7x7dNt`MN!Go9%Ej0F
zDC-ahxq3oHL4LmBxD5pFL=B0vEygcORqa@k-A|6<DZ=mI*{}){T9?W!4Kiau;;sr*
z`$=9vb=*#yh31|MeC2wG=nX5!<GrRC?1T|;g@k=^bU;sqlFsBtIRC0FryR2u`$S&`
zH?KQ<rRSm3S1#Yo@HKZXiDr4pA(LT{vif<}xl;gYxd_1L)LR~px=(by1kJLZ$y}el
zq5*@>eqA$bo1Ho$?dvFH@}zFBuY3ua0|E-@ewY7jxV2Klg`Bu+P)F#vTCHDd@PO1-
zzOyL3nB`f|rN~SN-jhv?$~V7ax_CEU`A3c>n#~)4IK*P)S<+`S#-@pGV>LdP)X3O|
zq+0DSJY|^6VHJ^y##9k`b_cwduxaV1g%H80B@ig3Y;j@}vb0C}nw3*&u2g53P2;So
zLo018o0#W7TY}=`>Vap#l_l??>@$%W`V3<c3ENusiGt_F5T=qCjm$FMf3O`Mikce0
z$G%bwEmJvYppH<o3{`r`BN(Oo2zg(9QnCKjeTmuRUvxxx`3VHaiEoGfN<lzjp=SH=
zy<mYh*ZU;Vxf=y}1vPIs6KNqAf{5Ok{u8I03626G_cyMd1y<v?*m41DQ$j}gn-pI&
zyP$Yj&JlNRrt-+uxSBQ~*TOb7@wtu=BLLBnKsKp(SnB$YZm!2!5RYGcv=@1OaI;N+
znp0EK#%6#-X$V=iJNNZ=r(Lj&?qe=Y<p)JNE~$xT9;i2!S8lCT&4Xe^#|&sb&A8J+
zR25mkpA~z<RDpA`E6kV9PTmjU3XfoJ?8t~KE>5&St+J-(P9w)ZP~R6phZ~}g9p)?u
zzG?{?i*YoK_;hxZ!x{8p{7lLcqj>xUR_4-r(HPwjTAUJ}@I@8P*I19y4-?F4hfs;G
zp*zcg06(bh>0vQrD*HhR6?R51{{n>kB*cRugA43P_g6K!LAwy7nVQs@k#B(3B3L5X
zCKP*|>9k}qCeYQTRH8$?mP}|I6qvPS=<HF{#H*Br)o^&EPA-wb4q9p>Ji&`^D1`~$
zKxE`HL}f!p<;!M@b2ldEYD(H<ifJv2bGwk7Y+6i@X4sRR<(J$7ootAi(DOyE^&1oL
z$<3XY)Ml*};M{^V$9T!HUl1H9Khmu95lo$M#y%oH@Q7Dh>UXOgnBA^Q#2whU0=J1p
zn==&%Fn4DLykyG&H&NI>x+Fco6&m9UKx-N|ElIr+u(Zrok*t`S%AU&hYnu(q`1WGf
z6}iLgj?)#K2O;5tyRwH-;tJP^31Pww^nPQuvqQuZPb^V(=dC;e4@BAx1*51uPgYDB
zTTh$!Ba|zn50@PyF;+!F6H$RV5TyBfGma##B*;rTFinUIwQX2k8+z5{y-r<%*l%QM
zU@bXKfmJfTIBm7r?X5YR!gWhZCsbchn+OCY(OnGZoIwVciw8eCV(gCJf5Y!NqX+h=
z4KB**0!P8ti0O>&^=0tESGV1selP%G<OG`~Crm-8PXTQi5!QgCFZCgq?f!(b&OYIU
z@|6|J=AD~}7^xRPCZY2S@;$oovLgS~Mixb7H*2<YiuT8OUH<ahk$eu)Dm#?6;>xL@
z0WY}KPZBOe_%1?N=Q8*87D#i{pFvW;Xnd=!Eow~~19P7RR!(Al0e0ki&>9$u+I~G@
zo1B|97Ig6PgN+KM*)&i$@-+ulo4-ISTN+HjD0HQ~cyM?J<>0n1gIdVQtoh5P$7RqY
zhe;{^+LwfHPCUH}Zari%r^K$_Ir@_$NXucq@;H?5sHoiFDDy@AP^muOF6k0_qcaIT
z_pbBg)76oOeTdY)5i#L*wsAa9n4>lnc>W6M0a@)-=qq2jnSK+CI*1J-sh4=IdCYbC
z8LBx^vn4x{w<$hd!^`xq72<%6r<)^qYLRf%5yel9Q0m`+jxT9CH?>j8fxE<Uf1J>E
zJ|y?wNGwz!awY?o;wQ&|Iu?UY5^PxzU!Q@5K{3G*O+6J%l}ubgD3c|;d<1)OZRbDn
z0GYH#<j*TO?k-KCR@eX(vbN#wnzlFYbf<ZbKR0pH6tTLYb@_5ll5bmOzyB-OH-9-_
zDdbi7TwM;TcOr8@$z=L=L=TLvFs-cO9U1LHRwS?13Qd4oC>S5O{jYDPLubaXD7#jq
zSoMYCA*$iPJhA28nApIM&x1EU@wSCkv95pUBmEF(;<E~feg}D{>6zjw%qNO`d#82}
z#bP~S-VU+2Dr$Lr40c~6$t;n|X>7scl{k5^2c)DiOV40<;Y`xI#gmu+l72YkelN*5
z2SRIFPNg%&u+2^4&TN}bv!=Q<s=fe&oP|d|dD@vAzhD_??Cb8;3(jNpyglIRuaKE_
z;GOwzCY3LJ$cgPp^U%jDUf@5$XS)#aUu02&J;5X_Ss8PBWVcp<E`q+c{6_N)=XzI+
z@5Ma_wO52gp6gk6Jut@bK3^vZ#B#u=jDzfoeJ{V+z<UIsYO#X`sA!8I1N}LQp(7BA
z#Kz(kk<$=_iE{oJH^&Guq7n-Eu-k{Cx}r+D7@*9)t?5-M0Y#43?WNd@rJB8{H{!HY
z6G9u^P^sc<qn_+Q4}G5M%cS{6^bWHSsJD4rH+i9o)GZ0xr~Jq-R$*Xq>1SAg*Z3xk
z@0kg|38l(;`B?Dg4)0r05<kkiF*wnM+rlvOjBzl|i+tMpAR+i*9FHkB!<gm=i)j2w
zz^lU75#!iB-`9~H-XDp&e1!w9FhBd7F!Z7vY4$htTNPVkinSo=yE8zTm;gYCTXaXh
zOeG_g!i+Hddjg%{-Y2OJC)MMjmy;P7PaoxN$3Bxj5qz7_VMGvOI=(8LF8iQ9iWtG?
z=Xm)VRrQot&~lDhQ5ln4_S}f_vbLBAJk;NGd*IdOV=1Xea4C8?u83A+MTjm&?n(r=
z2Kyqw6HmwuZIQ?L#<$;ST)XURqu+ItLo@n`17?=LBj4hu+&26R5o5lfYw%k3X1jf$
z#ZP{Z<hAU6mU!crcvh!;&rUbLMc|!$4=}7!0XD*!|AncFRKO6RC-8?Ggxw!@tFnau
z^O^2{YQQ<#2gjid{siO84-=o08_X#04ml`{Vjt4V=wyaBQ_kuna!zuq<xUhLnHxgT
ziD2-mq2zsaj4z{%>nF~e<u%T|RlB9Ej`nXrulqv5(WbDglMg5)R2WN$B3$6e(G}yB
zXqmPTJ!=MeN;aL@x}AG5;A)5(cNPs-97ZTKYt79Z){Fv(?1s<N*V($a?W-T86NuUl
zH9+abyYC%q48sarcVi72H|Lr(cDiC*^`#*+WW$KO_6**I^;-e=-{dv#y40&WxRe;g
znc|1zhGCHtk#8PR9qGn(R4m}Uzz|p(U|`a6>0Ps(+R^S_hV<WJRW@Y9dZWprZ>tpt
z3(}4u^r5kfVLEY1-h?R|o6RlStw-`@ZW!IoTGW?McdN~3--^g@RKT6wW)gAf@g;hd
zz!^&MHj?88cPi21Gr;bsnWb?Jp#17LtiqbVb;gu2F77bXhmDW~wYqB!zb8lyB@5gU
zzGptG+v~V+O<$eYQDEwGKx6-++9wm(q<2NweQZ;orE`9}BEZh!qJmY>EPGjbK}~kY
z=)OO5^pvBsS$11-;MkjwGeURtC`;&^(6V$*MBqa$dD%N6SpQUcOci^dJ-|yc3}k{J
zp<q06V2%)K=`LWJqzLABi7y<gJUiZhIG!8;ydb)CO1TM&D2Y70&lwT}*)072^359p
z?o!xovgJ_5cz7YA;i729z#612q_XUNN_VmQrW`L(NyGV@1E5MQU@40^_)5eV`Sy{d
z0oxaim^o*ntY)#M?joXVg{HQFQ2J^#iN&)-W)`;c=)Cynr!i6CSrZXFl`9}v?ZF1g
z2i8oDsFrE*<P2Db_MDoWA{1cz_OjHlurYHc*q`!t5XL*fu%%dXlk>f@eK?Ifp80Z$
zO&A`>0{VP9U^C%g_4z-#`yYD<KfFK`PGrA(KTjdl$Jc2MB~hmODlO6(P;F8K_hF-u
zyg-2ke}X$vO*D5(Tj||@e+&AUAWS&X1MxX0+^K*@a0&F*Zf}*_?v!V5Y~+lO&-2BH
z%>}~p?09m$f*1j{KszeM$>gAX9U?e51C!!O+5&5pOsz@lx7{Ft95-pOO?5nvF&gkr
zX|sm=OO|#iI-O=g^?R{3x-E{4Lkt?nJacJO7L25(+CmKabLJ|gXAO@2Pl;z7f{tGf
zmlrnM>9#0SAe~OU1=e1}aT`#&?=*K+aK3^BJyAEyT&qpxwc_Y1=B`(|P^UX((+^``
zM(}^j5kc`Hr2z8nyui{%)|^Y&eK@+*H~#{`iY+te0ieJ+j7b;Xtm0IW<0)SjB@=Wk
zXpE1Hu;;E?paMC~wfvve&tnfCpk=IFA3U{MYj?)`-Ka;Tf+KY>xok0Gc4H!GVRf~O
zjGY02p=e-HR`RpXmSlx)xia4=^}|UsR)$6%B~N~i&#|RCgzW=(y>h$d;xmn;^D(;)
zi*Q=LPs2Na7d#F6$j!ZMfO%xslz($M<kCX73_fw<ludzVQc$$}X)Ey6w?^1+vzRjR
zeG=NTvMIB9LlNU?IQhnDxg2F`7nn9R?!o??PLPh-*?Q24**Bd2pi_(kA1H_4rGL4D
zhec@wr<AML8H$V0-JY@#w&4AMsmj<v5+jALvkp_>DY(QdDjDb(Z&Zs*^bIZUi7jx<
z+?{)ah0v@@r&s7~I>MRZ4mCFuz08*01Yh6`JSq#Ag8Q9N=&0B_T&!4EPRdS`UJrcp
zda7?)1_=f8I0N1&e|LDQ&!uIKcVkSK#qxoU=w@?@iBYml$t2?lwMI`~b1de{DUYTI
zHoHkc%stHCcOaa}Hx=IjVv1tJ00D9TI;{Vx74vU4K>@mePS{GQkMOY4Oewp*IjrPm
z60m5%aAox-bS#on)9(gFib70i9KE4ZPz|jOgN2{`&dPV(-X6hNXiBMMeZzZ%cgXS~
z)UV7}>)O=3UNAbd@_I=3xZSFF>jhyA#wX4Zv_zapmfnlES}NWcCP!9EJX)A_KEVTp
zRVty{--pO{euvo3pU8d#@i`w9p&q=F$KS@imx8<1g>aK-?Mb|6j@TCVF|WYv8rhE>
zcR?31F(P+}A$G#vp-p+PWY>f2Ex81t9%}rTL|s$&Rb=SvOhZP^Pnd>O+Dr+$Y7rPi
z>aP+S%oV8S(+cF3WHc1Y4Bo$yza(aFG2e$ERU8HFR+ZpU=34%+n3}^J9z>GjvBMvd
zgw}@oX|bRnN!+0;-&L#*t*jIstH8BX4NSpI4VM=4zFfID1(Xw&7pfj)u|QYdKMZA~
zf=e%OVeTlde!}*O`h=Wm5*<bc8q>06oP#_JuLW6#$+TWMvVv+xc)KT^NrjaXidII#
zH5cEKYA3b%O+{+40cSLD+x(@mU5|ZG5eu1c6&!x}JZ#?~QUng3pQ43P3PF0@{+J^{
zdiUY^M$>Frs(rV;RAOUCf~$Z9G3q&-0eeY;eXnEn!xqlPLgCT?)yMK{xPgVHm^Bp`
zN8(L%`Juxk7rT7!_a{4>URqt+D)cVJ*0|R-qNB7QoLDovjJ+^Ce3oJXp=~fl<rWjp
zUltgQyi@Pd!#Wc}ib;~^m+zx}D_=#E+oN(VY|M0hE-b7j3f2rMB#O2+#PG4AlMai`
z6gJw%`%Hh@W+3ehYs|ivhQ!x}%cqc91N9n*;biu7TrwqJ9bnkF4lzvj)T+y|#qr!9
zlg-NloF?7spHW#=DAiLHKG1BW@9wTGDcOD*_(Tz0e$*9x++T0@cB#Afk)^0|ykt{R
zVUq~)v=j(ybW_E$QKO3?yRMirp{*PaZ-#X}@y#IF<LnH;1+g1pUuPym;znV<{V=xx
zW9Gi^rBEdXn#m*SwDChkrJGY$ui<#{xsc4YGv$H(Ckjr?Q!)(0{ij~e&ry(lRy=iQ
z<Fv9oD>ApcS$^LHK@iA`uDH7h3p&rr-g{p(0zm@P!^!eZ9U8gFah>Uu8`MfJY9G>z
zdUSlO*~+l$Oj|+vfKm;2JF5$KJEzNnhJ!i(IA;a~To(swAKJc$L>z_t(SAxD#fKSs
zA}VGTQYo9^#IV&l8vW*x!sSLGE6lb74wj7&PbXOC+a!{HU{<A|A2TfdMiZ4Zq|XMp
zD7uHh+9%Ag3HwV(J#BNoUeeK4fwv$&P(=GE`0SsR%?{2ms*JWj9%Z-l$NSDhBB8+a
zbOrSYg?>IktQPYR6RQ*sMn}xI3Lg)!MoTh&`40Am5-cg>*7oGq_R{JCB<KoPtwr)&
z%WTg_3SGiBqYqfZa-AfoBkeuamxOGjy>|(;wrP{@EZ4|eaG+UNBNlOLMJH;4)jMik
zbb>1f{Zn5q8F_8)r}_RqaK&Y`qO}6%W<tQ+O#AD=^@~&HUr$>^C}{kjv`Bpe-3f8N
zCaM4>Mh@LjC`Rk+5>Q4Wlb$?qKPw?_wZQL_G98i^^e6K6+w2g@Xkib)mj0*Xm-(m?
z?kO3Z4;g3fYn;p-Pwr1I-XYOY>A!w0FHPhth;Yx4mAexdnkGS;7ww(dM@LI;qv3h>
zp^>KJX1h9Xvpp078rf~c+(t#9pnCW^dG(8bk&6Ej9nF3bbhobd@CLMDvuBcT%^<B%
zg==JG_zqJ(sbm+jwweIT+j&Jqjk=@wWfdfJuL)CgyFIieHmcaNc~(P9Cb0oK%y1WO
z-}r5sdcFO??nCl9vx4!rO#M4oN-Y!bl(pViu<+<;D84kYwo<@;&5%r=o+OMfizuAV
z<71zi&V$j~sh&D>L=!n-YCWvL5YHDC8yQy$H~1(eXB*U%qiZ^W16EYLVr%-fDH;#6
zrSs5Nf0Dsp2MnapR0TKaL~0QGK4!)muLhc2wxm3boIP3XD!G4db++-7NU%4HN<&jx
zjX8Zo_qB7R+5_hk6YYSY0yVy=GidB9>@EqH@(CuM2^JS`dnolTz2;XdvaIpv!rcl~
z#@A1ME`bJmZb@?=f|AA}15bl#R>9gd3KIfp#Hv;~?+Fv^#Z2C5`9VJowwH8inX$Ba
zNKd8>j{D{M48ELn@|Hz{C6;wF<t$60dLtU|$Y0<LneQAvFiVvC7U|*meZ@HS8WAOl
zC?`oRAkcKV?(`l(cU^P|mMtAx2a%`usvc>5^&wF#U)WnjZb1Yr4}w4iij94wNu;m7
z*F{=3$t=F6MT+mUZu^S_@lo(*k``zBsqoKBj_hNfHe;{iX*VDWxns9<Y{#Q<d&wEm
z)SX3<OjP1XFU0mE)*YN^cB9ETN!ni%;CvZeVKH>avNsaydqYWsbGaVt3-X}~LI0~>
zQr{hX#6tZ?PJ)D|XVZ6mXQf>-;3+5uoM!!3dKZ5@1_Dkxwg!JV!2Ct;qD%>}R+UHO
zX#lugJ656Pc&<a}A~mBzvknwOBQui1rB1Va^lCU!$7pETJ)MmbJBRwZg&-pwLCWim
zV34kns6Lr0vY9nDHj-jj;Iw=C`1R@=gttp6MRX4w99Q)h6T$acY1rh9`9u&xy|v(I
zfZY>TQieiZZIKuvHM=yK(`l(hJ*My^%?U~t(26C)97BzOeobdXj|&9D1fpHOhSp<E
z0(*>dElN_?QCr%lm)+e(*3OOY<z3n4%?_q)WFXvShBK&g&NBrAuLsL)TDk(rz7c*H
zaKvJY(!NhTGH=}_eogbRT-e%v!cj0^+q9SBuK|C;w3HR{Xx2QqrY!Tt-z!)LGllx(
zT&4A$U7<YXLzgXfNmC)L{7dZWjZD&r9tTIwy<kF@(uId_`(50;s|CuA8o7p+YG0h0
zc=pwbvPlAJBWqO08H1U*EcFz7b0f)UOH?PSLRkX?UhhO0wNus8ZrtrCvx+mr-oQxJ
zal<lGY`uC#fStU0A_>1(*Q+oVP4q->RJ4f?f=S-3!p*&=z0$aY2j@s@K8FSF()PF>
zLGK)}M0Y_ytay-rC|{Uzp+0)qdfbJpwH$(?Dbkcj|2Uk$bFg53^C2VC>=kZd_yo?I
zLMJ28ZrffTd-az*BDt@PqXLbq_XcQ?h$Y!9q|f}Yj%kQRJqae{1n52_exXH(7$xjA
zWE8A|-T4rBERy|=l$E&01?v5a3+Ov!H0FX1{=4ywFoANVsxXS#jgSqd^4FOg)P2C2
zJg~L$B3sP4!mRv>9zw@>Oc1Y~@+p|7UB2?Xfe=3iFTbvOUnBuuAIza~I7#PdALe5T
zI=95S)yO|fxI^mkc8mszYZ$kj>I+SrP$nW&HP~jG2>QXT)!zqx3Q%!aEP&UZ(RX9S
zuLJ*IJiMMc=|#xPNOkigdH{T|rz?snJ`uA(i&n@`iOZt`Lqn}L95kCtuvf3dKe8b_
z0(r$Z%H)8?31TE>xYV4r>Fn;^9N~2GjN#eir6Z@4D!`P(<*dOJnt%KnPqop3S-!xr
zBLL-wC+DIITmg;56*cVdg`HB=<j3rbBaxFl$V#z11BD=|s$Z)}j0tXo>wJxz>84W<
zK+lqvw=DPOpo5WTFHW8<jgb{9f3d0N2rrMVy*#5Y2vuZB1x+{Pjq!e`8^tBKO^4~L
z(u_CpJ6s!bOm+7dWK9#UP?~Gys{X?#V`&xsr(Q5!qmG+?=y%~XH4BPq{KoGbQt1Ry
z=+aIWN)680aB)~G-zqMrO7<(mTdVe%ZA13=x_yE8d;JPcpzj_6JR;ozZv_8J#!k?{
z(8|_8)Wp`#Uco{4NB#ft_S>s$p@5<SI3-_gZBt_`4wq0rFYd>$4JlB<j|hVljT~YT
z&o<v6qdGOCKeu3B?3Kfp^VBs~>z7z`tzmSZ|MZ?OcgoI550bp{qfNov)8Qt-q4}my
znQqsQS7%oAKzTNA`_x7<at*abG3{`%u#nLWXxG(&q#DQ0Ly~FjN+IXru$YuNXTdAS
zY!v#yc@r3eWg^qV5$id=;5K00pPXH}+6U^;8mT8J_P%i{Q7_h<QBbQ<R)|<hw=`~0
zDk)dP5~K$lBA{n1m2>TWE5@<A!lr3xnkF!m+JfDUr893jEt%>dMyE80Jd|Qk*IcIF
zoXj2(n>~!EJ#m_rSYM#w&@xWR>cK>b6PTm6P^?~I2AXz3igj`rEQ?J^z~2%)<$@k4
zFhx0EY_LbL&}?GuKE|*exuRNY99NMsxD0Xy!K$PbAzA!Rqgj5?UcI2T$my0G-gBlH
z@__5>L<2M4tB`H@Ww6v;1Q~@ka7#{n(>Ut2a2rjwres(#pBJzQYm(G1HL~0XyKYP*
zskO$2Sh;P-deJb3-P8)H!MM1mozgH(XzAU-zB;YLHbF!##8|z%EztGnVEswZq4PSy
z&JrKi@!NHiZ^G^8oQ;#CB3=$UpMD?%!(bp+^%zFv<LazdGMQ%S*7kTao0|0~IAxHR
zf<8#SL2NlDLf&_TG6nn5@9RhszOg~t7B?8RB(ZMFM)6(@TNKB*^$plY(qVT3dkI7v
zqAkF^SefpGA?H21p`7Q(18tYWqpFZCUraVPYQZ*bYBjo~I#(VpcA=!BHwZT_LlHBT
zSEh%F_u{CZgutow(MAm$=Vhlx<5k?2WFTO7!Tip(F^{dIGkt^(ALnHf8eY3Dn%P&R
zg1<EqL`#2DQxHpgq2;{FtzD#C2k*2%v-#};++codZv7j=ZAXtxdJhfpWj6E_cQ}||
zZtK(lLe_`t%Ny4yLw1wXLhX=VuK;><x#N#d%%dL8S&LJ;5n4h78iL+h{Sf*%1*~0q
z@$TJN@^xf5-C~XNm-p=8<6>>~2t}B3l{}Xgy`Da2SobKOK6>1OfWS-!Us6n#-%ZN_
z;yS(TGr<l<(spT%`l!2Kd&xb9`fad_8T5wX(v~16cRmI;Mw@8OWii(cKYiaY<Pa{r
zS}DAWm2{qq$=q)>j5FxbAW1c>JrGJ7*@7`OHwNLwF<~Gmo=5z)gf8|QL{hb8m&R9R
z;?s6wbdK4|9oF6G84QdVlhECaQise&_Jb2r;V-xJ_S`gW+%<cHrYoWyjSe{7Gil`y
z`^nBVwfWPvd#MhQaJ`*h8WG`l!!(LCGF)Xz@Vd)<V~_nx@b@t!4v0JDyJz)Rz!=T>
z>lm%zVrOq)@o#Jj8L`8X-TbJ5pXN&}>gs1rXGE4+1yO{10x+V{G2YO>KJZM%pNb(x
zA)gXll~nPCA?OT3rZ7P)66jG&XTJFKG@9Pj>ebr*qVt7$5+Onau{mmvn*S)UIa;;|
zq1VtW@=s9ui}%s|^~3$**^5T_tI{h-p>ABZ@o&!T!nlN}Ras<vO4BR7JLR)rmTJN9
zxQ*4}nIUutC)!Y!#fY%LwN9I-am2zLo7feILs5WPaSU{!tvm)B#j+=6AEN9<WrAGm
zIir1D>Z|KLRoy75?5w5Vu^YUs1eH>zg}YT$bso$Oz3PZJ6b4hw1`+|z6U)Z!dEKvq
z>a+Di(xTkhv%0L0fQ3`w#MHU0ZlV&dY|5_N7$7@iG@X)biOBJ_pS5U7EBmFW59)Mm
zhg~HE=heFWFmT9N?q)T13N=cC-2**8V8;PUl6qIK3f=&X#?EhuhOr%b2K1EFx}g2C
z)|;&(Cxzag4s4V*m%GRqaDSPr4qeP^cC7vc`u8TKLo@b;05oR{z*xleYm@$E+Wp~J
z{mb$)L1E2m@-^zCEEOt@UFWhQ3ko|f1KXiHGo}n_YI3v%lp%s!3x9WWyoTKp-96vk
zW*9l|EyOM0jmu&|-w)JeGLzsY{mgwYeQC|h^AXq@s9zL&iH&J~EZ1}k%aL}Pjs-GB
zCG#?O&R?<3Mv#H~<2YHVBhLZ8cKAd2J>@;Go8-zn!mlVfuPo%!4efUW{Fkd<=ajg{
z1ka~D5$2b0wL=OeA!w}dN2JFf3#~bTx9{2eo~sVzlt*tX6;3Yscd5D`w3rv|?<;JB
z#a3#cWrj&@+zLjfYv;Y<G-a2CQloE$mVjcNq&3=vr141?tf`cuB)^ASEwl?Ap=xQ&
zz%t*BhS}C3tQcs|nENzo2feL0z|C1ZPU`)-gg%hV_{ah$D+{Ig2}P#T?PCCT;~7ue
zZA40Pq9r8)jGC{K7Yf_8h763G%kg{T9FcjA0-n>;HSfs#y;DNsR#ne0{$;(RE0l@m
zer}jy#?}S&)&@wvY8xFiAm3COE+L(voaSv+zlcV!etWTnLj!L9XjmIJG*Fc(2dr8S
zOwKI82u@uk&A*s#V7zZgb%nP(r@@fMI3kK1+u5^-ed>d0jfbGS9z-ehHd1WvIJIi@
z%)*dcVT96$)_7o4VpA%yFRv_R`wME;C2JN-W;SvmPsCP(b&>%V6>pJFZ!PEhzz14_
z8eyUbjK6m)(R#=m9>8j22<o@?$6veE|7|P%m&L}>o2l1`kFpw&c`qcvdbo8BvgvBU
zkSd+2lwQ7}R27+co700$2^9x^j^+-u24ZCYI>qMeUU<}|5XY$mG^$iu7sFOojho|*
zyOC2(3unip`vW{-pa{}vFv}z|+JtYgc(nk1ptV$IrT3C1Gqqr6vNtzypNO-KoPY2G
zFg~O>!*rU}v>@Toc*bOeCaKXti3?fmXh}?7dlivc36+upy8ioYOq*(a71o*7eukMN
z^T&z+GHtr5Fo5Q?bL@R_*LWA$g$52y=((cyU|dhuWR0z-1e;<zJv7g<R$zUPnF1rZ
z())LY^~#~qu#@2hRH97u7|_sa5ghlJ9PmM#mxc&*Q4%sPq_8s0iHru_(pIU(<xt{G
zwTrDDXjC#?t_x}Eace^{t))A}SW~a-G+Q+%_l{k#*zVR7BXH$3vG6@OSE?xrB?YB0
z3})KfEKo2$N56nQGWxQS(H*ANyWxtlBNa~X2kWCg#u$v{F+4yWM_rK|$&~gY6CcVm
zJ#Jm7#9IuRoRRFEdKWA{vpuJQpU%icjL~9qft6W*JiifHg1=Pb2wvLzMTgdR^}z08
zWFlS>tOj$i4hjc}Ev%oCSh_zKEfyyxALF1bRp;PF6vgU(-u4jOIn@+V3hQLc<%+9O
z%YO|SE?OiRWNIAF+rr!+%}S6G$9^}>L7WXgMnx_pcMvUJQA(v|_heoq-U%F0Op?>a
zXW5!Az@0djqMqhdUm&kSvv{Ten|X+)J=)4~^{8^#MIj)9@oh(ApQqEEmv=nauDf>+
zXY`0|QD$mYMiDxYw;K&Mz-i-A+zn&V#A27LKA7toR69`sVvtK*?3yDg%)56&pBSqf
zt$9@Rn>V;~Fu8Bm`aQ7REzP0kt<U}4W7g+00af^z0-^!OiN+cTo0O%Kk1e>+apH$-
zDr_gXjhlPk$DHa5Fts%Xpz}1t8QC5aKHN~6V(d*Hn{axXDEBFW8=)Ga-e00?Nh=tC
z&3o570<TxT7qlP<)o2a$GCOKLxfiVL11E;y{E%r!0HZgIFa;&Z=;h`i>7Y{X+$&<&
z;I27-3SG<>UZ%Z>6m3cSTA#(#VNvig3_}WDZNB~cQ`t|UK>Gdkj4<Fs0Qmff0suJb
z{{I~Tpqh#G^{){CcK)XO{{k2l1Q53z5)#N3pwtE6{g3KBV08F#Ap)HUeE%vVq9{No
zAu9?vsQ>q5-Djd$-(`yw0Uy-w|NL>~`Fwv{{7*6&0a*!A5e3DUGNOM)FnmS~nC*UC
zh(O;2QUTw;%OU+!3czdAUsAOHNby5J>7Nn;BuoF2@Zj$We*|Foh1u9w-|#))uQPxT
z$&cjUZ?!(Sfc4j3q?`T(_%kZhpGxb`bJV*UFpCP9=dA%H|K9w6UbQ~o8{?k<pReiw
z>pN!`0S9|yfcCA4-cMoY=Zt&Ds&>TynkB%8_)j{(z1GJOkcZzeO8i-dKVCAQ<AD~r
zu4n@CDi26a_cy$E09)~I@WgC&^vw;F0P^$VRu%@*R#s*X*1w0w-PBs@1%TWGdg=EK
z;Xkih9|%A!uHQfdG=I$<>;UJ=L`}>M1oZR_?CfN8EOm_jjI{7P?;~*9qTdy??*R6^
zzvmqf5YqHF1S)?7<^Y&F3{8w20P@)YDf!>$UETxFWCu_sSb)4U{F!mUz1HUr@wd>=
z+fy76EW-T1Wjx=7p;;M_aUekLd>=9XylQ=D(SM6BZDsUdYhldxC=?CgXJQE`^Y>#>
ze_pjd9e@xZzo~`41-<)y@i|uOS8xE2*fC%fAp8>$aIf{j1pNFBCg4z@y^gv0@6lE|
zr9*lEm6-q-nEu{QHh_Nd8#F;HEBo(ZH>_oJOe_I5*Pp>Qo_7K9*#^=nKpT+&iuyfd
z!k<^Ik2fIX-*2FQbO8}(13icTsHZ<i^~cUDsRp150#K>{gaq7ceNu#ei~7ey@dNvN
zrhi`%&m->s30><GC;MC6AJzKbhM(sV4u9g=Dg74jZ%zN-;vGJ(jpxzxezJI|{FddP
zN1W$DbAH0jy#3GQ`C|b7?F9PQ{CyXxd>-`XC!pcG{~iM8Im-7`-yg~E*Y~h7e_Y?^
zF;kttmGZlU&GW?1gUI}u9MEBYP5h^uz+aZ@&y26n;hzTv`3X;||4ZKf0{i3TlJVEx
z_`Tkqhv4{$ifHgJQ2!wy$MgI@4|VX93EB8xF#Xk02v9ofzXm>dPW4=A{wLL==|55Z
z7v1^i@XsaFf5OL`{}cR=miQ+D_2*R2wO4;q)!Y9Isy{02e^u9W`O2Rp^G^SQ<e$YX
zpO@&l+TTwybhm#Z`|ED>bIj*jQ$I0}z5faGM}zzU{l|~LZ0<d0c`j=7ljXqo7nc7+
z`&HuTIp%Xgg`b!oLx08m_wowQd7dBg|H+dO{tM5qgU7!*^8cLk`BCkkoI+9moAdX>
z)pMWPpO~jP{~Pl+9=6Xj{oH}^C&6akzaaSQDE0diJa-@aNzhyP3&EeW#J@58J}=30
zcaNV$o~6GK{nB0k>2Z6$h5i%Szv36<Upvzu(7)eMe_n{^TbDnnoT~qc>VM3o&v)#8
z!p_zF6YP%x;~)0#o)_S`UH>N&PUA03&)*;ZQg{EOK+kQ>KWR>zexdmb?2iKdar=j{
z@43<OC%#DQzrg?RCdlXY_1uo~lW3{qKMTYEW>tAD1qOHv{2pC~5eN|=J*VCI{n!5k
DXC5Z7

literal 0
HcmV?d00001

diff --git a/bbb-web-api/gradle/wrapper/gradle-wrapper.properties b/bbb-web-api/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 0000000000..8d130c8894
--- /dev/null
+++ b/bbb-web-api/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Nov 27 23:09:32 CET 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip
diff --git a/bbb-web-api/gradlew b/bbb-web-api/gradlew
new file mode 100755
index 0000000000..9d82f78915
--- /dev/null
+++ b/bbb-web-api/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/bbb-web-api/gradlew.bat b/bbb-web-api/gradlew.bat
new file mode 100755
index 0000000000..aec99730b4
--- /dev/null
+++ b/bbb-web-api/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/bbb-web-api/grails-app/assets/images/apple-touch-icon-retina.png b/bbb-web-api/grails-app/assets/images/apple-touch-icon-retina.png
new file mode 100755
index 0000000000000000000000000000000000000000..5cc83edbe69203eaaf7d64e5b2596de6e01fff29
GIT binary patch
literal 14986
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
Um-X{?+yDRo07*qoM6N<$f~LyXuK)l5

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/apple-touch-icon.png b/bbb-web-api/grails-app/assets/images/apple-touch-icon.png
new file mode 100755
index 0000000000000000000000000000000000000000..aba337f611dcedbe09b57f2757270a58fed1e656
GIT binary patch
literal 5434
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

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/favicon.ico b/bbb-web-api/grails-app/assets/images/favicon.ico
new file mode 100755
index 0000000000000000000000000000000000000000..3dfcb9279f60f2396a8f19fb6607ff281800c905
GIT binary patch
literal 10134
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
HxB~wLBPR<!

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/grails-cupsonly-logo-white.svg b/bbb-web-api/grails-app/assets/images/grails-cupsonly-logo-white.svg
new file mode 100755
index 0000000000..d3fe882c4b
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/images/grails-cupsonly-logo-white.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="500">
+    <desc iVinci="yes" version="4.5" gridStep="20" showGrid="no" snapToGrid="no" codePlatform="0"/>
+    <g id="Layer1" opacity="1">
+        <g id="Shape1">
+            <desc shapeID="1" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-74.3391,-50.75,148.678,101.5)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(4.79624,0,0,4.79624,500,250)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
+            <path id="shapePath1" d="M527.264,491.011 C544.051,488.613 563.236,483.817 572.829,479.021 C582.421,474.224 589.615,467.03 589.615,462.234 C589.615,462.234 587.217,457.438 584.819,452.641 C580.023,445.447 575.227,435.854 563.236,409.475 C558.44,397.484 547.589,366.072 544.051,351.92 C540.386,330.773 540.051,308.254 544.051,287.171 C547.531,274.839 552.314,262.919 560.838,253.597 C570.402,240.945 581.622,228.467 596.81,222.422 C644.094,203.599 699.929,162.469 728.707,116.904 C738.299,100.117 742.876,92.923 746.372,83.3305 C755.023,59.5988 762.66,34.3876 762.28,8.98871 L762.28,6.59059 L498.487,6.59059 L232.295,6.59059 L232.295,11.3868 C231.901,74.2274 269.048,130.868 313.831,172.061 C337.813,193.644 366.59,210.431 400.164,222.422 C412.154,227.218 416.951,229.616 426.543,239.208 C438.534,253.597 448.126,270.384 452.923,289.569 C455.827,317.286 453.654,346.577 445.728,373.503 L440.932,387.892 C438.534,397.484 431.339,411.873 419.349,435.854 C407.358,459.836 407.358,462.234 407.358,464.632 C412.154,479.021 440.932,488.613 484.098,493.409 C493.691,493.409 508.079,493.409 527.264,491.011 M325.822,409.475 C342.609,407.077 356.998,402.281 361.794,395.086 L361.794,392.688 L359.396,385.494 C342.609,354.318 333.016,327.939 333.016,301.56 C333.016,287.171 335.415,279.977 340.211,267.986 C347.405,255.995 349.803,252.125 361.794,247.329 C366.59,244.876 372.313,243.95 374.711,242.478 C380.979,240.625 388.173,236.81 388.173,236.81 C388.173,236.81 383.868,235.884 379.016,233.486 C364.628,228.69 359.396,224.82 347.405,217.625 C309.035,196.042 285.054,174.459 261.073,143.284 C253.878,131.293 250.156,125.996 246.684,121.163 L244.286,116.904 C241.888,114.506 145.963,114.506 143.565,116.904 C141.939,150.478 158.03,180.057 179.536,205.635 C204.661,235.514 225.101,244.005 244.286,248.801 C261.073,253.597 263.471,255.995 270.665,265.588 C275.462,277.578 277.86,284.773 277.86,299.161 C280.258,320.745 273.063,342.328 258.675,373.503 C253.878,383.096 249.082,392.688 249.082,392.688 C249.082,395.086 253.878,399.883 258.675,402.281 C270.665,409.475 304.239,414.271 325.822,409.475 M716.716,409.475 C735.901,407.077 747.892,402.281 750.29,395.086 C750.29,392.688 750.29,390.29 743.095,375.901 C728.008,346.118 717.597,310.72 726.308,277.578 C731.287,264.162 737.689,250.182 752.688,247.852 C776.669,240.658 795.854,229.616 819.835,205.635 C834.224,191.246 847.61,166.971 851.369,152.876 C854.382,141.577 858.172,128.066 855.807,116.904 C853.409,114.506 755.086,114.506 752.688,116.904 C752.688,116.904 750.29,119.302 747.892,121.7 C745.493,128.895 735.901,143.284 728.707,150.478 C719.114,162.469 690.337,191.246 680.744,198.44 C663.057,216.559 629.114,228.768 611.199,236.81 C613.597,239.208 625.587,246.403 635.18,248.801 C654.365,255.995 654.365,255.995 661.559,267.986 C666.355,279.977 668.754,287.171 668.754,301.56 C670.08,334.844 653.109,365.67 639.976,392.688 C657.022,411.883 692.824,411.394 716.716,409.475 Z" style="stroke:none;fill-rule:evenodd;fill:#ffffff;fill-opacity:1;"/>
+        </g>
+        <g id="Shape2">
+            <desc shapeID="2" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-3.75,-28,7.5,56)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,417.25,99.5)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
+            <path id="shapePath2" d="M413.5,127.5 C414,126.5 416,123 416.5,122.5 C416,123 414,126.5 413.5,127.5 M421,71.5 " style="stroke:none;fill-rule:evenodd;fill:#669020;fill-opacity:1;"/>
+        </g>
+        <g id="Shape3">
+            <desc shapeID="3" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
+            <path id="shapePath3" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#4c4c4c;fill-opacity:1;"/>
+        </g>
+        <g id="Shape4">
+            <desc shapeID="4" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
+            <path id="shapePath4" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#000000;fill-opacity:1;"/>
+        </g>
+        <g id="Shape5">
+            <desc shapeID="5" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-84.6928,-47.6497,169.386,95.2993)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,90.9499,90.9738)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
+            <path id="shapePath5" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#0d0d0d;fill-opacity:1;"/>
+        </g>
+    </g>
+</svg>
diff --git a/bbb-web-api/grails-app/assets/images/skin/database_add.png b/bbb-web-api/grails-app/assets/images/skin/database_add.png
new file mode 100755
index 0000000000000000000000000000000000000000..802bd6cde02d442288490c5f278b225e192927b5
GIT binary patch
literal 658
zcmV;D0&V??P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!BuPX;R5;6}
zQ%#6cQ562(Q&BW2BGWREFkvimY9~XqC}z=S5Zu(Rw*9QyOfZ{hpf&?7qJ<C<2w?-!
zs)ZoaLdIDXHq8te6@!-2!_s`OF-6~dopa&FgGpL+;p5(O-ub@sbKg~L+s6Max6>$<
zN?sD2BV4j9Yl`*+fsWQD?H_4>L?~r48B=l;Spkuc)A?yA6iP)R5d;DO`2BwH_g=3D
z!!XcjG|+Ch%jCP5&1Rc|$N`LEvA9yN*EyX%X^loByIQT<g+k$p<f*b0GERiU;eYlV
zjYdI9F6^2CIfbI8Y4G`c@Or)Qcs#<_@AuK^bkJ-z5s$}3>_h>#+l_9wi@{(Z?D2RE
zUDq)j4#hY2{Z&Br<Z`)01su$vzJ-V!Hj_=$oLm71OOm1}B3SxVRh?Xc6p#c4SHm`w
zYuX!p`muqZRUQ3bI|r6;-CuZlcYSe}00%ReC<WerF5|oBJ<gp?<H-I5R<>R!Yn$4g
z^!3C0RHpz#W@n--_jThHPEAe2R834DnuV#1kUlxXv}=C^n7~&>f1<cT=lZ5=@F;sQ
zm%JhDo9A<gA58Jl=WO9}rBZp8OeO=dSWJlU5V;Ctr-!Z9D%M+N%+Ef?z2X)4Tq4Op
zalXcUK7W*i76@6I_+EYs+)k<X9NpjsW>RO6h@8fUuT`wRE91*{Z%LW-oO8KckjTdf
s77ewwyuEar+*b)ff<bt=_MkQY8}h55LWjJWc>n+a07*qoM6N<$f>LlT?EnA(

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/database_delete.png b/bbb-web-api/grails-app/assets/images/skin/database_delete.png
new file mode 100755
index 0000000000000000000000000000000000000000..cce652e845cde732ac3ce9a4132b597301ad660e
GIT binary patch
literal 659
zcmV;E0&M+>P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!B}qg<R5;6}
zQ$2_iK@|RGe@Mt8!W}Uy5%)s;iwD-07NTe&!89H=iiM!n34(=)g<NkX<hbG>ps_J1
zHhTmGl@tMUh=_=a3Fb%)BqZVTW3s!xH?UsxE?7A5@n+t<@6Gq#%m~l(@IOPFT;%il
z03|#}Sa4nU2-$-Kn!4}FekQv@$S0FY$L9!N0g;c={9!m8K4zLG48uSu6aw$J+ii5a
zT~sO+G#ZW9<hzhcrK*z10Sw!=UzE#bKF*sqMx&8eDwVu!HoNNc6kiH0<|C2FKWmOg
zqo701Z0i9zWyX5Fj$kl|Kp+5J*QKx5>!I0fqFSvY9*@h|sR=YqL#x%oU@(yD@pz0*
zr-R{eDEHX6V*<Y>RaK<|4rWl@GKt@8COeKZT>%ICBq4+h_I-+?Y*V28oxmqBc+Oz5
zc>4@kzJgDe<1lkKXYEtktsNC`FoTI)4qLaF!_1E&4qv>EKx`iUcee83)!MzalltZ#
z3K)~8`*H_w9^uf5^9X)<39-6>(AOuJvu0IKc#FRkFoCa%ULtC>8v6bIR-H|{S~CWm
zy|LB2yL+L!Vs5g8ONBz=aUzj0EX$JbfSV}a#vT*B_2)32Uc<0oLyzLS9V$=7hM4?~
znM@`|iEa~8)bZW?7q}d~l*7K(I`+@}gr<B)w=u@Iy6TJu-W@URJEzm>T|_=WaH**u
tj~DMRZZ<crs*bQ6)vFTg!D)NE^b0!jsgNOE4w(P|002ovPDHLkV1f`lEXM!<

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/database_edit.png b/bbb-web-api/grails-app/assets/images/skin/database_edit.png
new file mode 100755
index 0000000000000000000000000000000000000000..e501b668c70c8e8a6b1142b0dc03bf6f26b59418
GIT binary patch
literal 767
zcmV<b0s#GqP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!kx4{BR5;6p
zQ%g(}Q4~GXPCK;}Cjo04nm|hZ<0oQdfySl5NJtZ+(S$^0VZv67k;J7j#;prozyd=e
z?2PV;jUgfp@x#wVv<S4Zlv17A4$MqDof+SoFa!d`o1D3K?z`vQJM-RS48s7mXf(=^
zxQ*btuIopLKS)4A{VVZv#P<<I!{M-oO~z0GLOw?PORv{^((m`9qM`zBw;OaVpU*=S
zMI@6+WV6{(lDpR4-k#b}phb~L<Xs|>pzVy^OjT85Vq(G=8XD@S91FH}kp`d7hyPh1
z5CCa%X>*RH50W(1GMNlqE*ChCgTvu4bCM)M5Co)BDTG2HvvyYjmSvI4<)A2v`CcxU
zQ79BpEEdf*P56K_c;lUWy=bic9s#4IZm`yWps?HR<^;5uf_%3rLf1Uy7}ym7{u3SG
zgQxL#;V@<+y-&7GK#MIB!!Xb^&5SuEhPoOV9{wDJpWonQN~qlHho`!h-y&cUDCjiw
zot3{JSR;b3Z$U9V2&bDtVsaL$Qu?FFtIb;kXm<)qqyl<=9Kq@&_)r^^)N|OJWjH)_
za7i;6X_aj`duRB^h5&`toyKA^3V-E1_yb`=eg>PPj8Y+p^vFkpk)_tg?(s>=HO~R<
zNVkfdL~{$}rBQI|9QHR{MrpYhcBg@2p$?h%pAnUsbBDUeKUv#oTc6-&EEZdf$Kwze
zM&QwZLDd6D&pd?=1#1F{N2f6?Hf8gwvt`>|pf)ft5F|nm_AI~XywcT&?}K--v^WN?
z_7vo-s3)9F{TXfF{hpqll^q2vdwBb}datvKg-yfc+gC^|#8-K5)%lB$rlxi}-rEGO
xUZ|2A>wRp~(I5;*aZFyx-fDe3J-^%i_y?+(!m^mX(n$aS002ovPDHLkV1gwlT*CkW

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/database_save.png b/bbb-web-api/grails-app/assets/images/skin/database_save.png
new file mode 100755
index 0000000000000000000000000000000000000000..44c06dddf19fbda14efe428b9b1793c13f46b2cf
GIT binary patch
literal 755
zcmV<P0u23$P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!g-Jv~R5;6p
zQ%!3VQ4~Gz%}f#}t+rrHAYz4DG!+$!R&c2+L9~!f#4ZF8>3^_07cLZBR}_>&jXObH
zw2it@svr%qE?kJ(Xuudu+DSW|WWK!jNvbU^UO02#+<Et$^X|-uFbq*B6rz;R6D|fp
zaES5%ftJAs%AY9TBNRqPMtp1vF*`duNSme-iNw)VDusAF4%0Nj#LLUeXf~TDm&>Tt
zYOko4%Vx8c4Gh!M(=Qem7g;XcE?n0Qi^XD?&*vX7@xPFCIh;%;@xMr?(;$(vo9j9i
z6;riZMJyIWG#Z6r7^-I5HtO{{DwPWQ`}>&y+Y;!yjz*&a$8prX=XtO!3$0d5J>%Mz
z1f8>Jnx-7^X2#7Yb#zC2VYfZ>c17@L{s)8{OuWBa3WHFfVXfhLv2t?V0V~q5R2D*D
z&315l_#iF}b>Zoo?-;+7*`WOJWsMw(x3WXv`@U*s@Y-&<s5d>edFEYpz0skP)dFfu
zZ4wIp&Vbb!+|0+3Qa}p<*AH-eY>3q8s6?RA)zqP8W39IT5HLFG9m1F);gE|P`L7@@
zctjKsn1rA6!ZZR%R^(SjU!r=2o$yGp<$KViK~{B;AIcgvN+J+&Nvur+W(Sw&=H?z}
zGMRW^U!Nl3AvWzQ3~C%Z*G*(?qLfNCq;tpg2yRW4@yl9;p3CK)O-@c8Sy))OUMiKc
zQp#QYFZe-<Gz|n;!~OYd*lq437ZNr58?K(XL#r4Knr6UuM@L7$P`bjHn3(8ZSy?%t
z>*@LZDInR^#F=Bm=!vA2i6tkEJ#i0aggzp2D%3!>h~r~3uLt(-IMoyFA<H}I7%l`2
lScE<TVdG=@Gk#hwegmFXvIr+J^MU{X002ovPDHLkV1g+|PZ|IK

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/database_table.png b/bbb-web-api/grails-app/assets/images/skin/database_table.png
new file mode 100755
index 0000000000000000000000000000000000000000..693709cbc1b156839a754e53cbaf409edec69567
GIT binary patch
literal 726
zcmV;{0xA88P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Xh}ptR5;6p
zl37m^VHAevMz8%7{t<tIQ4{0Bg<cq9G%+p-Q9?)qS`n8<jg>T&uF!>{(iS?1OX-eX
zKw9bunxR5FrF6QaYs~9>A4#zW^dwIvCpq(+cfR?U`T6-{9LHUqo16RKcDwUVr?cX4
zIN~hJDs48~aRAJ}U_2g=KAB9SP$;0;Y@*$6Ly{z<(`i^NmbL#1W@l#$wOS3;YPBOE
zJ;7`?L*?Ga6XzC292wl75}>gDz`(>h?is$JPxm#<xLmGWxm>0jGnotoK|nAVM5$DQ
z!C*kO-aeF@+Ejy?nVHEp8V&F~k7BWicsx!aH9kHLRpcQ?L&JFBAB4i&kAaVUxVvzh
z3a-EY0%m%8nhI7|SE(QpiBL#sG#VUMM9}*(0mg2(Q$Zq;z|PJNd_Euiem@;jtJN@i
za|c2MmsL?PR;yKNwOUA}QuO=7;V@#c7!{~gs?J7hAlsE7U#g?$aRkhSTqLq6iuCu9
z10_j_=;?Dc?4cZ386qH0HkgHTDT|HmGR`W4V2noNQJqfLJEot)q{V_UtsW+m31cP~
zDwWEi3HYBSoF4M;T?VaIdqinn1HZ9}32qs-PdwPbCf+WI6n9jl0-8cjV3%1FB%B&r
z+`mzSliyLSH0dxYE}rk&=!uCa*V>()2znj`_XYjtbt>@4FLHnJE|G`xv)Ba@oLBny
z1%3K7c4fiB^4{k6E8Pif0kNy62}b@9+<KEjq1=Ed)v;BmK@h9*NmsyUN_6-jE+~D!
zI)WRT<l?ngE7dD661em%ii@KWoKuEyW;lS83h#lOxWlo(0eDnbil$wtx&QzG07*qo
IM6N<$f}6%b00000

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/exclamation.png b/bbb-web-api/grails-app/assets/images/skin/exclamation.png
new file mode 100755
index 0000000000000000000000000000000000000000..c37bd062e60c3b38fc82e4d1f236a8ac2fae9d8c
GIT binary patch
literal 701
zcmV;u0z&<XP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Pf0{UR5;6}
zlgmq#VHCz6ztjnp=~F?R5pQFPl&gp~;l?gdL<B>N#0$9Ug7g<e5Ea#;%|NSKc)@5f
z;7$;Xq9UY)Lyb<UqnJ8&8J!fJX6Aj*^R}?Q8M8KZcJJYw-+2!2dGd@g_@BuCJt4QR
zG|-rKLw1p@BngtANcK}WWZ&tJ=GN!sDp0pB`wdNCE3eQ~*sve@ZufS6V;#vz1Xgl=
zg_Pck`%i+q0GXSY{D$mHSJV_2H8nzF3t(&k0AP7)V0mk?F%>~-`rQ^qx~m@y2OU8A
z#zh~=7n#Z$Z*fx-GOtDf07cgx0suCz_W(2~Y(0tf@FX@P6EPuM_dgn$vj9LucO)%W
zw%HgMW>=#oL>nZ>M&NEf08>)#)k<{$fCT_r>rPi=BV=hFh6WS^qqze>C6Ek}o{M5%
za|@JGow<Oj0^5dOoQKhLCOSWpAw+HGQ?pSN*k#a4S7iZ8!-#Pje@#@=;p__PCqxJc
zp|wJe0=1luufg)vI#NXu-(QYlbNI0{or=h>u0t{&hgNzySHZxy@LTNh);YzZ2zSp_
zl$^T&Dnc|NLb&RD_!4>pt@VHdP)ZGER%5ZmWEe$lryR&y;2u^3cOkO<wojkI*Ki1l
z#hIAadT_@fO?4jgQ>4#6c%-<rxdo~DIizNzFvh@D?}Mw}hj=)IkFQ6!NOre9eIML)
z%AxZ|{Xz!zmU%tpEr;N;OJNL<O041Y#5fX5vnwQfk3ahm{G%5DVEpXi29oU*c9F8v
jOytms=QDpd)4#+ImC>(EY6a{600000NkvXXu0mjfxS2AI

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/house.png b/bbb-web-api/grails-app/assets/images/skin/house.png
new file mode 100755
index 0000000000000000000000000000000000000000..fed62219f57cdfb854782dbadf5123c44d056bd4
GIT binary patch
literal 806
zcmV+>1KIqEP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!xJg7oR5;6x
zlj~1XVI0TLtMf<ruC2>v;U&v3%|^C`Ga3?LtY&4dQB4Oz;1v;J%z!D&%WRH@BZ?x;
z3)8@IUIv@hG|@IwyHLC`l{1<4BK>wam95g|i|?Cfzt876&-Zx_0f5*l-9`IJI&mHu
zE6$@xB)6N}7VeR;!X8D!TAw;;&0Bsj?A071cO>X3K0wl7WZ1;Tg!4LHyNcnzoeQ7t
zNW`aSlm8WX<OyV-Dk(Bz$-(49867z$-AV*I%Q4y_MxQP%;;Bv}w-k7kiDZ9SGBw)3
zL-3IT{zd`3wdr_j$iQ%u7~}3L3|K{Qm$9k)id6CsLBBeg^p>Ykek&ir$13=ngczvf
zV0vnjNpCF&K8px}dunv+`LIb-sOC$_jD(;IBI$xC|7`(+9cA>Vir_V#z{?k7SX^Ah
z^71m~W@q439Ycqfhi7+gp#A14n1n1!e>$EdeATG|f798Y=ggzwEKH2Q!qU<b1OLkG
z+ZnVJvT;{-5?$sCXe`V?IcINxpqu4-^=a$F&3Wk2W^R0VH@CU--}&_MJ!*1TXt;P3
zWtn@{>2QA(Se?dwqG69%>n$6rtE<egQmKSWrGi?mMrmm&vlfj;VKLo;niD@2ga5fk
zlgYF~Q4}*A16x~L(bm?6%F4=3K};%<NcL^{4h#mvN+c3thAS#6V6)j^x7$%uQ?n_M
z%jFOXg}((w&o3twbe%gdw(|zW7XCNKP&;B0jYb29!vUw$30lmoJ$PeBL8k;_8*e(>
z%F(845Az8c{w(XgimJg96!jLMz?zS6I1HUm2baqQx7&@nx;lhHA!r6vs2|fqJETOu
zLxe<Ge3YPf9;9s{+7>u2OQ(3(au%dg>AcZsWI(zXn9XJg1cLe8k~0h0wOL=&HK}7X
k{AKr*U4z7Szv)i%9gTgghwgU$Q~&?~07*qoM6N<$g31kYk^lez

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/information.png b/bbb-web-api/grails-app/assets/images/skin/information.png
new file mode 100755
index 0000000000000000000000000000000000000000..12cd1aef900803abba99b26920337ec01ad5c267
GIT binary patch
literal 778
zcmV+l1NHogP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!oJmAMR5;6}
zlgn$=R~*GZcTB8gOeQf3nTa-!(WoeBp@~u~2*phyQae;ZqApx?m2DMV`5y=hK32Z4
zrF5FEg<6`FhR&iJnZ_{H#59l57&FNv{mtC(z4z-vNPM&#J<Bg&J{&mb7**wcRP*K`
zb9FQ=!5vo@PFAh7I1MPF2;ru8;m+XMPaeMdZ(eoq#g&nawQ{i|p~;@MMjF<x0bn6=
z3>BVme|mWaqy4$_pJm?y9KM{-*hp?1+Ey3e-CEDooTa!B;e(Q>TSF?bj>5At13y1p
zriN3w3x~5SfZj{@J4M{kp{?=M_Lh2bV<nsu_OvyDDu0g6V`7F~$@Tm*U*@N~g=sW=
zn!9%NQ4BzW>+5LH)Q)5W!-ePA$RgE1@5f1cyHki0Y}JyVEYZF(LD$xXlt$7A5CgE@
zpV-&l%vf;=5kZ2-2gi@Y6J&=cuwt>!vJ^#(&n|LcZyUzi6Duj$$hJ1s*HD-#;k-w@
zpdrwAuoDG_N2bvb07G$Zk*?Hc)JLtW4yqOnic_$zO7NZ#l>Fm){;fE?b$IbOaX2fe
z0la4g0Dfw2xk7Wi7NapVD8YMPCZu?A1QCK*67dgsvRKBLFtrM>?$%&_lD1882mzdO
zWPdw5KWw6IT`m1b_8=lS5jt8D3=RDa=&jWzR-)S@56WMslZ~mKu1)-wpXB>rNBQ>N
zU#K`#1B&v|_AQK;7I~B}OdGiUT9LX>f0xm6<;Le<?v8hEtmnbwS!SP?2`;dqv5sw9
zni(%Gaqov;mNB)bas%IF->P!=vFjPsUQF*wCJ*dO)4YBypgdiuF!=i@6Zyi7F|q#K
zz?tlSZULa@t1D?$e;f@b36&N!V2mjOHw|*<kAZXlKc;ul3#dPWds((2xc~qF07*qo
IM6N<$f<v}uGXMYp

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/shadow.jpg b/bbb-web-api/grails-app/assets/images/skin/shadow.jpg
new file mode 100755
index 0000000000000000000000000000000000000000..b7ed44fadc9c05e4dbf55614cecd66340c45d108
GIT binary patch
literal 300
zcmb7<Jr06E5QX2cEUbZu!;c`*B@*vpVM5f-%2;{_y@QR7C$jJm{xsfXT}rg_n`Daj
zWnMDoZr&KIn^glM!qN`R3kyi$x~`CRNhv))RK6t&f?mi`9CO5+499trmKk#~Ey~HH
zs;VTfXSJS<b*0-9vZuc1_m%4FlvDjT<_cEGK!R#GOoGYWkb@x5!IjF2(SYD|4JEwU
fTb0`9zGzeaO`iybtzp5K<FPxRwg-8<U~V2C1`a32

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/sorted_asc.gif b/bbb-web-api/grails-app/assets/images/skin/sorted_asc.gif
new file mode 100755
index 0000000000000000000000000000000000000000..6b179c11cf72c786630d98bf4b21fbb0d83ffd0c
GIT binary patch
literal 835
zcmZ?wbhEHbWMg1s_|Cu}C@ARZ>FMR=<?ZdAmX?;Do}Q7B;q)hB&z?Q|_U$`x;J}R=
zH*Vj)efRENhEXsY0>dr@6o0ZXGBB_+=zx3%$`cG63Jm-*84Da1I50Ew7%?y?G#+5$
UVU>wEFhP-tNtBU=gM+~u00(^_>i_@%

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/skin/sorted_desc.gif b/bbb-web-api/grails-app/assets/images/skin/sorted_desc.gif
new file mode 100755
index 0000000000000000000000000000000000000000..38b3a01d078418d3afcdb2765251a9f21b7995be
GIT binary patch
literal 834
zcmZ?wbhEHbWMg1s_|Cu}C@83_tE;D{=jG+)?d|RKCt}{bc?%XSShQ%-?%lih?%lh8
z|9*y1Fd72GGz1iXvM@3*urla?{0GVt3>@+doD32i4hNW;7z9Kl3=A9^nYh^GDt25@
NJj%i*$Hu~74FL5|8=3$B

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/images/spinner.gif b/bbb-web-api/grails-app/assets/images/spinner.gif
new file mode 100755
index 0000000000000000000000000000000000000000..1ed786f2ece49ec5db07dee13a56ef38025b628c
GIT binary patch
literal 2037
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
SXP~*+AU!zMlD%~Wg#Hic*k=_0

literal 0
HcmV?d00001

diff --git a/bbb-web-api/grails-app/assets/javascripts/application.js b/bbb-web-api/grails-app/assets/javascripts/application.js
new file mode 100755
index 0000000000..24c15880c2
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/javascripts/application.js
@@ -0,0 +1,21 @@
+// This is a manifest file that'll be compiled into application.js.
+//
+// Any JavaScript file within this directory can be referenced here using a relative path.
+//
+// You're free to add application-wide JavaScript to this file, but it's generally better
+// to create separate JavaScript files as needed.
+//
+//= require jquery-2.2.0.min
+//= require bootstrap
+//= require_tree .
+//= require_self
+
+if (typeof jQuery !== 'undefined') {
+    (function($) {
+        $('#spinner').ajaxStart(function() {
+            $(this).fadeIn();
+        }).ajaxStop(function() {
+            $(this).fadeOut();
+        });
+    })(jQuery);
+}
diff --git a/bbb-web-api/grails-app/assets/javascripts/bootstrap.js b/bbb-web-api/grails-app/assets/javascripts/bootstrap.js
new file mode 100755
index 0000000000..01fbbcbaa9
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/javascripts/bootstrap.js
@@ -0,0 +1,2363 @@
+/*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under the MIT license
+ */
+
+if (typeof jQuery === 'undefined') {
+  throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+  'use strict';
+  var version = $.fn.jquery.split(' ')[0].split('.')
+  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 2)) {
+    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3')
+  }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.6
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      WebkitTransition : 'webkitTransitionEnd',
+      MozTransition    : 'transitionend',
+      OTransition      : 'oTransitionEnd otransitionend',
+      transition       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+
+    return false // explicit for ie8 (  ._.)
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false
+    var $el = this
+    $(this).one('bsTransitionEnd', function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+
+    if (!$.support.transition) return
+
+    $.event.special.bsTransitionEnd = {
+      bindType: $.support.transition.end,
+      delegateType: $.support.transition.end,
+      handle: function (e) {
+        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+      }
+    }
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.6
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.VERSION = '3.3.6'
+
+  Alert.TRANSITION_DURATION = 150
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.closest('.alert')
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      // detach from parent, fire event then clean up data
+      $parent.detach().trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one('bsTransitionEnd', removeElement)
+        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.alert
+
+  $.fn.alert             = Plugin
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.6
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element  = $(element)
+    this.options   = $.extend({}, Button.DEFAULTS, options)
+    this.isLoading = false
+  }
+
+  Button.VERSION  = '3.3.6'
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state += 'Text'
+
+    if (data.resetText == null) $el.data('resetText', $el[val]())
+
+    // push to event loop to allow forms to submit
+    setTimeout($.proxy(function () {
+      $el[val](data[state] == null ? this.options[state] : data[state])
+
+      if (state == 'loadingText') {
+        this.isLoading = true
+        $el.addClass(d).attr(d, d)
+      } else if (this.isLoading) {
+        this.isLoading = false
+        $el.removeClass(d).removeAttr(d)
+      }
+    }, this), 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var changed = true
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+      if ($input.prop('type') == 'radio') {
+        if ($input.prop('checked')) changed = false
+        $parent.find('.active').removeClass('active')
+        this.$element.addClass('active')
+      } else if ($input.prop('type') == 'checkbox') {
+        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
+        this.$element.toggleClass('active')
+      }
+      $input.prop('checked', this.$element.hasClass('active'))
+      if (changed) $input.trigger('change')
+    } else {
+      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+      this.$element.toggleClass('active')
+    }
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  var old = $.fn.button
+
+  $.fn.button             = Plugin
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document)
+    .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      var $btn = $(e.target)
+      if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+      Plugin.call($btn, 'toggle')
+      if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault()
+    })
+    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+    })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.6
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      = null
+    this.sliding     = null
+    this.interval    = null
+    this.$active     = null
+    this.$items      = null
+
+    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+  }
+
+  Carousel.VERSION  = '3.3.6'
+
+  Carousel.TRANSITION_DURATION = 600
+
+  Carousel.DEFAULTS = {
+    interval: 5000,
+    pause: 'hover',
+    wrap: true,
+    keyboard: true
+  }
+
+  Carousel.prototype.keydown = function (e) {
+    if (/input|textarea/i.test(e.target.tagName)) return
+    switch (e.which) {
+      case 37: this.prev(); break
+      case 39: this.next(); break
+      default: return
+    }
+
+    e.preventDefault()
+  }
+
+  Carousel.prototype.cycle = function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getItemIndex = function (item) {
+    this.$items = item.parent().children('.item')
+    return this.$items.index(item || this.$active)
+  }
+
+  Carousel.prototype.getItemForDirection = function (direction, active) {
+    var activeIndex = this.getItemIndex(active)
+    var willWrap = (direction == 'prev' && activeIndex === 0)
+                || (direction == 'next' && activeIndex == (this.$items.length - 1))
+    if (willWrap && !this.options.wrap) return active
+    var delta = direction == 'prev' ? -1 : 1
+    var itemIndex = (activeIndex + delta) % this.$items.length
+    return this.$items.eq(itemIndex)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || this.getItemForDirection(type, $active)
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var that      = this
+
+    if ($next.hasClass('active')) return (this.sliding = false)
+
+    var relatedTarget = $next[0]
+    var slideEvent = $.Event('slide.bs.carousel', {
+      relatedTarget: relatedTarget,
+      direction: direction
+    })
+    this.$element.trigger(slideEvent)
+    if (slideEvent.isDefaultPrevented()) return
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+      $nextIndicator && $nextIndicator.addClass('active')
+    }
+
+    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one('bsTransitionEnd', function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () {
+            that.$element.trigger(slidEvent)
+          }, 0)
+        })
+        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+    } else {
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger(slidEvent)
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  var old = $.fn.carousel
+
+  $.fn.carousel             = Plugin
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  var clickHandler = function (e) {
+    var href
+    var $this   = $(this)
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+    if (!$target.hasClass('carousel')) return
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    Plugin.call($target, options)
+
+    if (slideIndex) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  }
+
+  $(document)
+    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      Plugin.call($carousel, $carousel.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.6
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.$trigger      = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
+                           '[data-toggle="collapse"][data-target="#' + element.id + '"]')
+    this.transitioning = null
+
+    if (this.options.parent) {
+      this.$parent = this.getParent()
+    } else {
+      this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+    }
+
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.VERSION  = '3.3.6'
+
+  Collapse.TRANSITION_DURATION = 350
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var activesData
+    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+    if (actives && actives.length) {
+      activesData = actives.data('bs.collapse')
+      if (activesData && activesData.transitioning) return
+    }
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    if (actives && actives.length) {
+      Plugin.call(actives, 'hide')
+      activesData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')[dimension](0)
+      .attr('aria-expanded', true)
+
+    this.$trigger
+      .removeClass('collapsed')
+      .attr('aria-expanded', true)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse in')[dimension]('')
+      this.transitioning = 0
+      this.$element
+        .trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse in')
+      .attr('aria-expanded', false)
+
+    this.$trigger
+      .addClass('collapsed')
+      .attr('aria-expanded', false)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse')
+        .trigger('hidden.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+  Collapse.prototype.getParent = function () {
+    return $(this.options.parent)
+      .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+      .each($.proxy(function (i, element) {
+        var $element = $(element)
+        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+      }, this))
+      .end()
+  }
+
+  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+    var isOpen = $element.hasClass('in')
+
+    $element.attr('aria-expanded', isOpen)
+    $trigger
+      .toggleClass('collapsed', !isOpen)
+      .attr('aria-expanded', isOpen)
+  }
+
+  function getTargetFromTrigger($trigger) {
+    var href
+    var target = $trigger.attr('data-target')
+      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+    return $(target)
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.collapse
+
+  $.fn.collapse             = Plugin
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+    var $this   = $(this)
+
+    if (!$this.attr('data-target')) e.preventDefault()
+
+    var $target = getTargetFromTrigger($this)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+
+    Plugin.call($target, option)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.6
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle="dropdown"]'
+  var Dropdown = function (element) {
+    $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.VERSION = '3.3.6'
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+  function clearMenus(e) {
+    if (e && e.which === 3) return
+    $(backdrop).remove()
+    $(toggle).each(function () {
+      var $this         = $(this)
+      var $parent       = getParent($this)
+      var relatedTarget = { relatedTarget: this }
+
+      if (!$parent.hasClass('open')) return
+
+      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this.attr('aria-expanded', 'false')
+      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+    })
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we use a backdrop because click events don't delegate
+        $(document.createElement('div'))
+          .addClass('dropdown-backdrop')
+          .insertAfter($(this))
+          .on('click', clearMenus)
+      }
+
+      var relatedTarget = { relatedTarget: this }
+      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this
+        .trigger('focus')
+        .attr('aria-expanded', 'true')
+
+      $parent
+        .toggleClass('open')
+        .trigger($.Event('shown.bs.dropdown', relatedTarget))
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive && e.which != 27 || isActive && e.which == 27) {
+      if (e.which == 27) $parent.find(toggle).trigger('focus')
+      return $this.trigger('click')
+    }
+
+    var desc = ' li:not(.disabled):visible a'
+    var $items = $parent.find('.dropdown-menu' + desc)
+
+    if (!$items.length) return
+
+    var index = $items.index(e.target)
+
+    if (e.which == 38 && index > 0)                 index--         // up
+    if (e.which == 40 && index < $items.length - 1) index++         // down
+    if (!~index)                                    index = 0
+
+    $items.eq(index).trigger('focus')
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.dropdown')
+
+      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown             = Plugin
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.6
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options             = options
+    this.$body               = $(document.body)
+    this.$element            = $(element)
+    this.$dialog             = this.$element.find('.modal-dialog')
+    this.$backdrop           = null
+    this.isShown             = null
+    this.originalBodyPad     = null
+    this.scrollbarWidth      = 0
+    this.ignoreBackdropClick = false
+
+    if (this.options.remote) {
+      this.$element
+        .find('.modal-content')
+        .load(this.options.remote, $.proxy(function () {
+          this.$element.trigger('loaded.bs.modal')
+        }, this))
+    }
+  }
+
+  Modal.VERSION  = '3.3.6'
+
+  Modal.TRANSITION_DURATION = 300
+  Modal.BACKDROP_TRANSITION_DURATION = 150
+
+  Modal.DEFAULTS = {
+    backdrop: true,
+    keyboard: true,
+    show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this.isShown ? this.hide() : this.show(_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.checkScrollbar()
+    this.setScrollbar()
+    this.$body.addClass('modal-open')
+
+    this.escape()
+    this.resize()
+
+    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.$dialog.on('mousedown.dismiss.bs.modal', function () {
+      that.$element.one('mouseup.dismiss.bs.modal', function (e) {
+        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
+      })
+    })
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(that.$body) // don't move modals dom position
+      }
+
+      that.$element
+        .show()
+        .scrollTop(0)
+
+      that.adjustDialog()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element.addClass('in')
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$dialog // wait for modal to slide in
+          .one('bsTransitionEnd', function () {
+            that.$element.trigger('focus').trigger(e)
+          })
+          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+        that.$element.trigger('focus').trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+    this.resize()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .off('click.dismiss.bs.modal')
+      .off('mouseup.dismiss.bs.modal')
+
+    this.$dialog.off('mousedown.dismiss.bs.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.trigger('focus')
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keydown.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.resize = function () {
+    if (this.isShown) {
+      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+    } else {
+      $(window).off('resize.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.$body.removeClass('modal-open')
+      that.resetAdjustments()
+      that.resetScrollbar()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $(document.createElement('div'))
+        .addClass('modal-backdrop ' + animate)
+        .appendTo(this.$body)
+
+      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+        if (this.ignoreBackdropClick) {
+          this.ignoreBackdropClick = false
+          return
+        }
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus()
+          : this.hide()
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one('bsTransitionEnd', callback)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      var callbackRemove = function () {
+        that.removeBackdrop()
+        callback && callback()
+      }
+      $.support.transition && this.$element.hasClass('fade') ?
+        this.$backdrop
+          .one('bsTransitionEnd', callbackRemove)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callbackRemove()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+  // these following methods are used to handle overflowing modals
+
+  Modal.prototype.handleUpdate = function () {
+    this.adjustDialog()
+  }
+
+  Modal.prototype.adjustDialog = function () {
+    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+    this.$element.css({
+      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+    })
+  }
+
+  Modal.prototype.resetAdjustments = function () {
+    this.$element.css({
+      paddingLeft: '',
+      paddingRight: ''
+    })
+  }
+
+  Modal.prototype.checkScrollbar = function () {
+    var fullWindowWidth = window.innerWidth
+    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
+      var documentElementRect = document.documentElement.getBoundingClientRect()
+      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
+    }
+    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
+    this.scrollbarWidth = this.measureScrollbar()
+  }
+
+  Modal.prototype.setScrollbar = function () {
+    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+    this.originalBodyPad = document.body.style.paddingRight || ''
+    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+  }
+
+  Modal.prototype.resetScrollbar = function () {
+    this.$body.css('padding-right', this.originalBodyPad)
+  }
+
+  Modal.prototype.measureScrollbar = function () { // thx walsh
+    var scrollDiv = document.createElement('div')
+    scrollDiv.className = 'modal-scrollbar-measure'
+    this.$body.append(scrollDiv)
+    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+    this.$body[0].removeChild(scrollDiv)
+    return scrollbarWidth
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  var old = $.fn.modal
+
+  $.fn.modal             = Plugin
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    if ($this.is('a')) e.preventDefault()
+
+    $target.one('show.bs.modal', function (showEvent) {
+      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+      $target.one('hidden.bs.modal', function () {
+        $this.is(':visible') && $this.trigger('focus')
+      })
+    })
+    Plugin.call($target, option, this)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.6
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       = null
+    this.options    = null
+    this.enabled    = null
+    this.timeout    = null
+    this.hoverState = null
+    this.$element   = null
+    this.inState    = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.VERSION  = '3.3.6'
+
+  Tooltip.TRANSITION_DURATION = 150
+
+  Tooltip.DEFAULTS = {
+    animation: true,
+    placement: 'top',
+    selector: false,
+    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+    trigger: 'hover focus',
+    title: '',
+    delay: 0,
+    html: false,
+    container: false,
+    viewport: {
+      selector: 'body',
+      padding: 0
+    }
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled   = true
+    this.type      = type
+    this.$element  = $(element)
+    this.options   = this.getOptions(options)
+    this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
+    this.inState   = { click: false, hover: false, focus: false }
+
+    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
+      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
+    }
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay,
+        hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
+    }
+
+    if (self.tip().hasClass('in') || self.hoverState == 'in') {
+      self.hoverState = 'in'
+      return
+    }
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.isInStateTrue = function () {
+    for (var key in this.inState) {
+      if (this.inState[key]) return true
+    }
+
+    return false
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
+    }
+
+    if (self.isInStateTrue()) return
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.' + this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+      if (e.isDefaultPrevented() || !inDom) return
+      var that = this
+
+      var $tip = this.tip()
+
+      var tipId = this.getUID(this.type)
+
+      this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+        .data('bs.' + this.type, this)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+      this.$element.trigger('inserted.bs.' + this.type)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var orgPlacement = placement
+        var viewportDim = this.getPosition(this.$viewport)
+
+        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
+                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
+                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
+                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+
+      var complete = function () {
+        var prevHoverState = that.hoverState
+        that.$element.trigger('shown.bs.' + that.type)
+        that.hoverState = null
+
+        if (prevHoverState == 'out') that.leave(that)
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        $tip
+          .one('bsTransitionEnd', complete)
+          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+        complete()
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function (offset, placement) {
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  += marginTop
+    offset.left += marginLeft
+
+    // $.fn.offset doesn't round pixel values
+    // so we use setOffset directly with our own function B-0
+    $.offset.setOffset($tip[0], $.extend({
+      using: function (props) {
+        $tip.css({
+          top: Math.round(props.top),
+          left: Math.round(props.left)
+        })
+      }
+    }, offset), 0)
+
+    $tip.addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      offset.top = offset.top + height - actualHeight
+    }
+
+    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+    if (delta.left) offset.left += delta.left
+    else offset.top += delta.top
+
+    var isVertical          = /top|bottom/.test(placement)
+    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+    $tip.offset(offset)
+    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+  }
+
+  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
+    this.arrow()
+      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+      .css(isVertical ? 'top' : 'left', '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function (callback) {
+    var that = this
+    var $tip = $(this.$tip)
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+      that.$element
+        .removeAttr('aria-describedby')
+        .trigger('hidden.bs.' + that.type)
+      callback && callback()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && $tip.hasClass('fade') ?
+      $tip
+        .one('bsTransitionEnd', complete)
+        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+      complete()
+
+    this.hoverState = null
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function ($element) {
+    $element   = $element || this.$element
+
+    var el     = $element[0]
+    var isBody = el.tagName == 'BODY'
+
+    var elRect    = el.getBoundingClientRect()
+    if (elRect.width == null) {
+      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+    }
+    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()
+    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+    return $.extend({}, elRect, scroll, outerDims, elOffset)
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+  }
+
+  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+    var delta = { top: 0, left: 0 }
+    if (!this.$viewport) return delta
+
+    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+    var viewportDimensions = this.getPosition(this.$viewport)
+
+    if (/right|left/.test(placement)) {
+      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
+      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+      if (topEdgeOffset < viewportDimensions.top) { // top overflow
+        delta.top = viewportDimensions.top - topEdgeOffset
+      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+      }
+    } else {
+      var leftEdgeOffset  = pos.left - viewportPadding
+      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+        delta.left = viewportDimensions.left - leftEdgeOffset
+      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
+        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+      }
+    }
+
+    return delta
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
+  Tooltip.prototype.tip = function () {
+    if (!this.$tip) {
+      this.$tip = $(this.options.template)
+      if (this.$tip.length != 1) {
+        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
+      }
+    }
+    return this.$tip
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = this
+    if (e) {
+      self = $(e.currentTarget).data('bs.' + this.type)
+      if (!self) {
+        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+        $(e.currentTarget).data('bs.' + this.type, self)
+      }
+    }
+
+    if (e) {
+      self.inState.click = !self.inState.click
+      if (self.isInStateTrue()) self.enter(self)
+      else self.leave(self)
+    } else {
+      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+    }
+  }
+
+  Tooltip.prototype.destroy = function () {
+    var that = this
+    clearTimeout(this.timeout)
+    this.hide(function () {
+      that.$element.off('.' + that.type).removeData('bs.' + that.type)
+      if (that.$tip) {
+        that.$tip.detach()
+      }
+      that.$tip = null
+      that.$arrow = null
+      that.$viewport = null
+    })
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip             = Plugin
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.6
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.VERSION  = '3.3.6'
+
+  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right',
+    trigger: 'click',
+    content: '',
+    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+    ](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.popover
+
+  $.fn.popover             = Plugin
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.6
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    this.$body          = $(document.body)
+    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target || '') + ' .nav li > a'
+    this.offsets        = []
+    this.targets        = []
+    this.activeTarget   = null
+    this.scrollHeight   = 0
+
+    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.VERSION  = '3.3.6'
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.getScrollHeight = function () {
+    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var that          = this
+    var offsetMethod  = 'offset'
+    var offsetBase    = 0
+
+    this.offsets      = []
+    this.targets      = []
+    this.scrollHeight = this.getScrollHeight()
+
+    if (!$.isWindow(this.$scrollElement[0])) {
+      offsetMethod = 'position'
+      offsetBase   = this.$scrollElement.scrollTop()
+    }
+
+    this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#./.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && $href.is(':visible')
+          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        that.offsets.push(this[0])
+        that.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.getScrollHeight()
+    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (this.scrollHeight != scrollHeight) {
+      this.refresh()
+    }
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+    }
+
+    if (activeTarget && scrollTop < offsets[0]) {
+      this.activeTarget = null
+      return this.clear()
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
+        && this.activate(targets[i])
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    this.clear()
+
+    var selector = this.selector +
+      '[data-target="' + target + '"],' +
+      this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length) {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate.bs.scrollspy')
+  }
+
+  ScrollSpy.prototype.clear = function () {
+    $(this.selector)
+      .parentsUntil(this.options.target, '.active')
+      .removeClass('active')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy             = Plugin
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load.bs.scrollspy.data-api', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      Plugin.call($spy, $spy.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.6
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    // jscs:disable requireDollarBeforejQueryAssignment
+    this.element = $(element)
+    // jscs:enable requireDollarBeforejQueryAssignment
+  }
+
+  Tab.VERSION = '3.3.6'
+
+  Tab.TRANSITION_DURATION = 150
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.data('target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var $previous = $ul.find('.active:last a')
+    var hideEvent = $.Event('hide.bs.tab', {
+      relatedTarget: $this[0]
+    })
+    var showEvent = $.Event('show.bs.tab', {
+      relatedTarget: $previous[0]
+    })
+
+    $previous.trigger(hideEvent)
+    $this.trigger(showEvent)
+
+    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.closest('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $previous.trigger({
+        type: 'hidden.bs.tab',
+        relatedTarget: $this[0]
+      })
+      $this.trigger({
+        type: 'shown.bs.tab',
+        relatedTarget: $previous[0]
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+          .removeClass('active')
+        .end()
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', false)
+
+      element
+        .addClass('active')
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', true)
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu').length) {
+        element
+          .closest('li.dropdown')
+            .addClass('active')
+          .end()
+          .find('[data-toggle="tab"]')
+            .attr('aria-expanded', true)
+      }
+
+      callback && callback()
+    }
+
+    $active.length && transition ?
+      $active
+        .one('bsTransitionEnd', next)
+        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tab
+
+  $.fn.tab             = Plugin
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  var clickHandler = function (e) {
+    e.preventDefault()
+    Plugin.call($(this), 'show')
+  }
+
+  $(document)
+    .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+    .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.6
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+
+    this.$target = $(this.options.target)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element     = $(element)
+    this.affixed      = null
+    this.unpin        = null
+    this.pinnedOffset = null
+
+    this.checkPosition()
+  }
+
+  Affix.VERSION  = '3.3.6'
+
+  Affix.RESET    = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0,
+    target: window
+  }
+
+  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+    var scrollTop    = this.$target.scrollTop()
+    var position     = this.$element.offset()
+    var targetHeight = this.$target.height()
+
+    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+    if (this.affixed == 'bottom') {
+      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+    }
+
+    var initializing   = this.affixed == null
+    var colliderTop    = initializing ? scrollTop : position.top
+    var colliderHeight = initializing ? targetHeight : height
+
+    if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+    return false
+  }
+
+  Affix.prototype.getPinnedOffset = function () {
+    if (this.pinnedOffset) return this.pinnedOffset
+    this.$element.removeClass(Affix.RESET).addClass('affix')
+    var scrollTop = this.$target.scrollTop()
+    var position  = this.$element.offset()
+    return (this.pinnedOffset = position.top - scrollTop)
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var height       = this.$element.height()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+    var scrollHeight = Math.max($(document).height(), $(document.body).height())
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+    if (this.affixed != affix) {
+      if (this.unpin != null) this.$element.css('top', '')
+
+      var affixType = 'affix' + (affix ? '-' + affix : '')
+      var e         = $.Event(affixType + '.bs.affix')
+
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      this.affixed = affix
+      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+      this.$element
+        .removeClass(Affix.RESET)
+        .addClass(affixType)
+        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+    }
+
+    if (affix == 'bottom') {
+      this.$element.offset({
+        top: scrollHeight - height - offsetBottom
+      })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.affix
+
+  $.fn.affix             = Plugin
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop    != null) data.offset.top    = data.offsetTop
+
+      Plugin.call($spy, data)
+    })
+  })
+
+}(jQuery);
diff --git a/bbb-web-api/grails-app/assets/javascripts/jquery-2.2.0.min.js b/bbb-web-api/grails-app/assets/javascripts/jquery-2.2.0.min.js
new file mode 100755
index 0000000000..06ac263150
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/javascripts/jquery-2.2.0.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v2.2.0 | (c) jQuery Foundation | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!k.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=R.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c}catch(e){}O.set(a,b,c);
+}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return this;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||d,e=c.documentElement,f=c.body,a.pageX=b.clientX+(e&&e.scrollLeft||f&&f.scrollLeft||0)-(e&&e.clientLeft||f&&f.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||f&&f.scrollTop||0)-(e&&e.clientTop||f&&f.clientTop||0)),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ea.test(f)?this.mouseHooks:da.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=d),3===a.target.nodeType&&(a.target=a.target.parentNode),h.filter?h.filter(a,g):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==ia()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===ia()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ga:ha):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:ha,isPropagationStopped:ha,isImmediatePropagationStopped:ha,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ga,a&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ga,a&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ga,a&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),n.fn.extend({on:function(a,b,c,d){return ja(this,a,b,c,d)},one:function(a,b,c,d){return ja(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=ha),this.each(function(){n.event.remove(this,a,c,b)})}});var ka=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,la=/<script|<style|<link/i,ma=/checked\s*(?:[^=]|=\s*.checked.)/i,na=/^true\/(.*)/,oa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=wa[0].contentDocument,b.write(),b.close(),c=ya(a,b),wa.detach()),xa[a]=c),c}var Aa=/^margin/,Ba=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ca=function(b){var c=b.ownerDocument.defaultView;return c.opener||(c=a),c.getComputedStyle(b)},Da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Ea=d.documentElement;!function(){var b,c,e,f,g=d.createElement("div"),h=d.createElement("div");if(h.style){h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,g.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",g.appendChild(h);function i(){h.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",h.innerHTML="",Ea.appendChild(g);var d=a.getComputedStyle(h);b="1%"!==d.top,f="2px"===d.marginLeft,c="4px"===d.width,h.style.marginRight="50%",e="4px"===d.marginRight,Ea.removeChild(g)}n.extend(l,{pixelPosition:function(){return i(),b},boxSizingReliable:function(){return null==c&&i(),c},pixelMarginRight:function(){return null==c&&i(),e},reliableMarginLeft:function(){return null==c&&i(),f},reliableMarginRight:function(){var b,c=h.appendChild(d.createElement("div"));return c.style.cssText=h.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",h.style.width="1px",Ea.appendChild(g),b=!parseFloat(a.getComputedStyle(c).marginRight),Ea.removeChild(g),h.removeChild(c),b}})}}();function Fa(a,b,c){var d,e,f,g,h=a.style;return c=c||Ca(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),!l.pixelMarginRight()&&Ba.test(g)&&Aa.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Ga(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Ha=/^(none|table(?!-c[ea]).+)/,Ia={position:"absolute",visibility:"hidden",display:"block"},Ja={letterSpacing:"0",fontWeight:"400"},Ka=["Webkit","O","Moz","ms"],La=d.createElement("div").style;function Ma(a){if(a in La)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ka.length;while(c--)if(a=Ka[c]+b,a in La)return a}function Na(a,b,c){var d=T.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Oa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Pa(b,c,e){var f=!0,g="width"===c?b.offsetWidth:b.offsetHeight,h=Ca(b),i="border-box"===n.css(b,"boxSizing",!1,h);if(d.msFullscreenElement&&a.top!==a&&b.getClientRects().length&&(g=Math.round(100*b.getBoundingClientRect()[c])),0>=g||null==g){if(g=Fa(b,c,h),(0>g||null==g)&&(g=b.style[c]),Ba.test(g))return g;f=i&&(l.boxSizingReliable()||g===b.style[c]),g=parseFloat(g)||0}return g+Oa(b,c,e||(i?"border":"content"),f,h)+"px"}function Qa(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=N.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=N.access(d,"olddisplay",za(d.nodeName)))):(e=V(d),"none"===c&&e||N.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Fa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=T.exec(c))&&e[1]&&(c=W(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Fa(a,b,d)),"normal"===e&&b in Ja&&(e=Ja[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Ha.test(n.css(a,"display"))&&0===a.offsetWidth?Da(a,Ia,function(){return Pa(a,b,d)}):Pa(a,b,d):void 0},set:function(a,c,d){var e,f=d&&Ca(a),g=d&&Oa(a,b,d,"border-box"===n.css(a,"boxSizing",!1,f),f);return g&&(e=T.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=n.css(a,b)),Na(a,c,g)}}}),n.cssHooks.marginLeft=Ga(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Fa(a,"marginLeft"))||a.getBoundingClientRect().left-Da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px":void 0}),n.cssHooks.marginRight=Ga(l.reliableMarginRight,function(a,b){return b?Da(a,{display:"inline-block"},Fa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Aa.test(a)||(n.cssHooks[a+b].set=Na)}),n.fn.extend({css:function(a,b){return K(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ca(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Qa(this,!0)},hide:function(){return Qa(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function Ra(a,b,c,d,e){return new Ra.prototype.init(a,b,c,d,e)}n.Tween=Ra,Ra.prototype={constructor:Ra,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ra.propHooks[this.prop];return a&&a.get?a.get(this):Ra.propHooks._default.get(this)},run:function(a){var b,c=Ra.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ra.propHooks._default.set(this),this}},Ra.prototype.init.prototype=Ra.prototype,Ra.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},Ra.propHooks.scrollTop=Ra.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=Ra.prototype.init,n.fx.step={};var Sa,Ta,Ua=/^(?:toggle|show|hide)$/,Va=/queueHooks$/;function Wa(){return a.setTimeout(function(){Sa=void 0}),Sa=n.now()}function Xa(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=U[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ya(a,b,c){for(var d,e=(_a.tweeners[b]||[]).concat(_a.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Za(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&V(a),q=N.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?N.get(a,"olddisplay")||za(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Ua.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?za(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=N.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;N.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ya(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function $a(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function _a(a,b,c){var d,e,f=0,g=_a.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Sa||Wa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:Sa||Wa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for($a(k,j.opts.specialEasing);g>f;f++)if(d=_a.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,Ya,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(_a,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return W(c.elem,a,T.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],_a.tweeners[c]=_a.tweeners[c]||[],_a.tweeners[c].unshift(b)},prefilters:[Za],prefilter:function(a,b){b?_a.prefilters.unshift(a):_a.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=_a(this,n.extend({},a),f);(e||N.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=N.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Va.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=N.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Xa(b,!0),a,d,e)}}),n.each({slideDown:Xa("show"),slideUp:Xa("hide"),slideToggle:Xa("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Sa=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Sa=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ta||(Ta=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(Ta),Ta=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=d.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var ab,bb=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return K(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ab:void 0)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)}}),ab={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=bb[b]||n.find.attr;bb[b]=function(a,b,d){var e,f;return d||(f=bb[b],bb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,bb[b]=f),e}});var cb=/^(?:input|select|textarea|button)$/i,db=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return K(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b];
+},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(void 0===a||"boolean"===c)&&(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){return n.trim(a.value)}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var hb=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!hb.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,hb.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!L(e)||l&&n.isFunction(e[q])&&!n.isWindow(e)&&(i=e[l],i&&(e[l]=null),n.event.triggered=q,e[q](),n.event.triggered=void 0,i&&(e[l]=i)),b.result}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b),d.isDefaultPrevented()&&c.preventDefault()}}),n.fn.extend({trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),l.focusin="onfocusin"in a,l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=N.access(d,b);e||d.addEventListener(a,c,!0),N.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=N.access(d,b)-1;e?N.access(d,b,e):(d.removeEventListener(a,c,!0),N.remove(d,b))}}});var ib=a.location,jb=n.now(),kb=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return(!c||c.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+b),c};var lb=/#.*$/,mb=/([?&])_=[^&]*/,nb=/^(.*?):[ \t]*([^\r\n]*)$/gm,ob=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,pb=/^(?:GET|HEAD)$/,qb=/^\/\//,rb={},sb={},tb="*/".concat("*"),ub=d.createElement("a");ub.href=ib.href;function vb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function wb(a,b,c,d){var e={},f=a===sb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function xb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function yb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function zb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ib.href,type:"GET",isLocal:ob.test(ib.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":tb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?xb(xb(a,n.ajaxSettings),b):xb(n.ajaxSettings,a)},ajaxPrefilter:vb(rb),ajaxTransport:vb(sb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m=n.ajaxSetup({},c),o=m.context||m,p=m.context&&(o.nodeType||o.jquery)?n(o):n.event,q=n.Deferred(),r=n.Callbacks("once memory"),s=m.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,getResponseHeader:function(a){var b;if(2===v){if(!h){h={};while(b=nb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===v?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return v||(a=u[c]=u[c]||a,t[a]=b),this},overrideMimeType:function(a){return v||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>v)for(b in a)s[b]=[s[b],a[b]];else x.always(a[x.status]);return this},abort:function(a){var b=a||w;return e&&e.abort(b),z(0,b),this}};if(q.promise(x).complete=r.add,x.success=x.done,x.error=x.fail,m.url=((b||m.url||ib.href)+"").replace(lb,"").replace(qb,ib.protocol+"//"),m.type=c.method||c.type||m.method||m.type,m.dataTypes=n.trim(m.dataType||"*").toLowerCase().match(G)||[""],null==m.crossDomain){j=d.createElement("a");try{j.href=m.url,j.href=j.href,m.crossDomain=ub.protocol+"//"+ub.host!=j.protocol+"//"+j.host}catch(y){m.crossDomain=!0}}if(m.data&&m.processData&&"string"!=typeof m.data&&(m.data=n.param(m.data,m.traditional)),wb(rb,m,c,x),2===v)return x;k=n.event&&m.global,k&&0===n.active++&&n.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!pb.test(m.type),f=m.url,m.hasContent||(m.data&&(f=m.url+=(kb.test(f)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=mb.test(f)?f.replace(mb,"$1_="+jb++):f+(kb.test(f)?"&":"?")+"_="+jb++)),m.ifModified&&(n.lastModified[f]&&x.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&x.setRequestHeader("If-None-Match",n.etag[f])),(m.data&&m.hasContent&&m.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",m.contentType),x.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+tb+"; q=0.01":""):m.accepts["*"]);for(l in m.headers)x.setRequestHeader(l,m.headers[l]);if(m.beforeSend&&(m.beforeSend.call(o,x,m)===!1||2===v))return x.abort();w="abort";for(l in{success:1,error:1,complete:1})x[l](m[l]);if(e=wb(sb,m,c,x)){if(x.readyState=1,k&&p.trigger("ajaxSend",[x,m]),2===v)return x;m.async&&m.timeout>0&&(i=a.setTimeout(function(){x.abort("timeout")},m.timeout));try{v=1,e.send(t,z)}catch(y){if(!(2>v))throw y;z(-1,y)}}else z(-1,"No Transport");function z(b,c,d,h){var j,l,t,u,w,y=c;2!==v&&(v=2,i&&a.clearTimeout(i),e=void 0,g=h||"",x.readyState=b>0?4:0,j=b>=200&&300>b||304===b,d&&(u=yb(m,x,d)),u=zb(m,u,x,j),j?(m.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(n.lastModified[f]=w),w=x.getResponseHeader("etag"),w&&(n.etag[f]=w)),204===b||"HEAD"===m.type?y="nocontent":304===b?y="notmodified":(y=u.state,l=u.data,t=u.error,j=!t)):(t=y,(b||!y)&&(y="error",0>b&&(b=0))),x.status=b,x.statusText=(c||y)+"",j?q.resolveWith(o,[l,y,x]):q.rejectWith(o,[x,y,t]),x.statusCode(s),s=void 0,k&&p.trigger(j?"ajaxSuccess":"ajaxError",[x,m,j?l:t]),r.fireWith(o,[x,y]),k&&(p.trigger("ajaxComplete",[x,m]),--n.active||n.event.trigger("ajaxStop")))}return x},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return!n.expr.filters.visible(a)},n.expr.filters.visible=function(a){return a.offsetWidth>0||a.offsetHeight>0||a.getClientRects().length>0};var Ab=/%20/g,Bb=/\[\]$/,Cb=/\r?\n/g,Db=/^(?:submit|button|image|reset|file)$/i,Eb=/^(?:input|select|textarea|keygen)/i;function Fb(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Bb.test(a)?d(a,e):Fb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Fb(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Fb(c,a[c],b,e);return d.join("&").replace(Ab,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Eb.test(this.nodeName)&&!Db.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Cb,"\r\n")}}):{name:b.name,value:c.replace(Cb,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Gb={0:200,1223:204},Hb=n.ajaxSettings.xhr();l.cors=!!Hb&&"withCredentials"in Hb,l.ajax=Hb=!!Hb,n.ajaxTransport(function(b){var c,d;return l.cors||Hb&&!b.crossDomain?{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Gb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=n("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Ib=[],Jb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Ib.pop()||n.expando+"_"+jb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Jb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Jb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Jb,"$1"+e):b.jsonp!==!1&&(b.url+=(kb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Ib.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),l.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||(l.createHTMLDocument?d.implementation.createHTMLDocument(""):d);var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ca([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var Kb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Kb)return Kb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(g,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function Lb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(e=d.getBoundingClientRect(),c=Lb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0)-a.scrollTop(),d.left+=n.css(a[0],"borderLeftWidth",!0)-a.scrollLeft()),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ea})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;n.fn[a]=function(d){return K(this,function(a,d,e){var f=Lb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ga(l.pixelPosition,function(a,c){return c?(c=Fa(a,b),Ba.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return K(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)},size:function(){return this.length}}),n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Mb=a.jQuery,Nb=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Nb),b&&a.jQuery===n&&(a.jQuery=Mb),n},b||(a.jQuery=a.$=n),n});
diff --git a/bbb-web-api/grails-app/assets/stylesheets/application.css b/bbb-web-api/grails-app/assets/stylesheets/application.css
new file mode 100755
index 0000000000..1889054bc6
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/application.css
@@ -0,0 +1,15 @@
+/*
+* This is a manifest file that'll be compiled into application.css, which will include all the files
+* listed below.
+*
+* Any CSS file within this directory can be referenced here using a relative path.
+*
+* You're free to add application-wide styles to this file and they'll appear at the top of the
+* compiled file, but it's generally better to create a new file per style scope.
+*
+*= require bootstrap
+*= require grails
+*= require main
+*= require mobile
+*= require_self
+*/
diff --git a/bbb-web-api/grails-app/assets/stylesheets/bootstrap.css b/bbb-web-api/grails-app/assets/stylesheets/bootstrap.css
new file mode 100755
index 0000000000..42c79d6e45
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/bootstrap.css
@@ -0,0 +1,6760 @@
+/*!
+ * Bootstrap v3.3.6 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+body {
+  margin: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+audio,
+canvas,
+progress,
+video {
+  display: inline-block;
+  vertical-align: baseline;
+}
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+[hidden],
+template {
+  display: none;
+}
+a {
+  background-color: transparent;
+}
+a:active,
+a:hover {
+  outline: 0;
+}
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+b,
+strong {
+  font-weight: bold;
+}
+dfn {
+  font-style: italic;
+}
+h1 {
+  margin: .67em 0;
+  font-size: 2em;
+}
+mark {
+  color: #000;
+  background: #ff0;
+}
+small {
+  font-size: 80%;
+}
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+sup {
+  top: -.5em;
+}
+sub {
+  bottom: -.25em;
+}
+img {
+  border: 0;
+}
+svg:not(:root) {
+  overflow: hidden;
+}
+figure {
+  margin: 1em 40px;
+}
+hr {
+  height: 0;
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+}
+pre {
+  overflow: auto;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+  margin: 0;
+  font: inherit;
+  color: inherit;
+}
+button {
+  overflow: visible;
+}
+button,
+select {
+  text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+input {
+  line-height: normal;
+}
+input[type="checkbox"],
+input[type="radio"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 0;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+fieldset {
+  padding: .35em .625em .75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+legend {
+  padding: 0;
+  border: 0;
+}
+textarea {
+  overflow: auto;
+}
+optgroup {
+  font-weight: bold;
+}
+table {
+  border-spacing: 0;
+  border-collapse: collapse;
+}
+td,
+th {
+  padding: 0;
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print {
+  *,
+  *:before,
+  *:after {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    -webkit-box-shadow: none !important;
+            box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  a[href^="#"]:after,
+  a[href^="javascript:"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  .navbar {
+    display: none;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+@font-face {
+  font-family: 'Glyphicons Halflings';
+
+  src: url('../fonts/glyphicons-halflings-regular.eot');
+  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.glyphicon-asterisk:before {
+  content: "\002a";
+}
+.glyphicon-plus:before {
+  content: "\002b";
+}
+.glyphicon-euro:before,
+.glyphicon-eur:before {
+  content: "\20ac";
+}
+.glyphicon-minus:before {
+  content: "\2212";
+}
+.glyphicon-cloud:before {
+  content: "\2601";
+}
+.glyphicon-envelope:before {
+  content: "\2709";
+}
+.glyphicon-pencil:before {
+  content: "\270f";
+}
+.glyphicon-glass:before {
+  content: "\e001";
+}
+.glyphicon-music:before {
+  content: "\e002";
+}
+.glyphicon-search:before {
+  content: "\e003";
+}
+.glyphicon-heart:before {
+  content: "\e005";
+}
+.glyphicon-star:before {
+  content: "\e006";
+}
+.glyphicon-star-empty:before {
+  content: "\e007";
+}
+.glyphicon-user:before {
+  content: "\e008";
+}
+.glyphicon-film:before {
+  content: "\e009";
+}
+.glyphicon-th-large:before {
+  content: "\e010";
+}
+.glyphicon-th:before {
+  content: "\e011";
+}
+.glyphicon-th-list:before {
+  content: "\e012";
+}
+.glyphicon-ok:before {
+  content: "\e013";
+}
+.glyphicon-remove:before {
+  content: "\e014";
+}
+.glyphicon-zoom-in:before {
+  content: "\e015";
+}
+.glyphicon-zoom-out:before {
+  content: "\e016";
+}
+.glyphicon-off:before {
+  content: "\e017";
+}
+.glyphicon-signal:before {
+  content: "\e018";
+}
+.glyphicon-cog:before {
+  content: "\e019";
+}
+.glyphicon-trash:before {
+  content: "\e020";
+}
+.glyphicon-home:before {
+  content: "\e021";
+}
+.glyphicon-file:before {
+  content: "\e022";
+}
+.glyphicon-time:before {
+  content: "\e023";
+}
+.glyphicon-road:before {
+  content: "\e024";
+}
+.glyphicon-download-alt:before {
+  content: "\e025";
+}
+.glyphicon-download:before {
+  content: "\e026";
+}
+.glyphicon-upload:before {
+  content: "\e027";
+}
+.glyphicon-inbox:before {
+  content: "\e028";
+}
+.glyphicon-play-circle:before {
+  content: "\e029";
+}
+.glyphicon-repeat:before {
+  content: "\e030";
+}
+.glyphicon-refresh:before {
+  content: "\e031";
+}
+.glyphicon-list-alt:before {
+  content: "\e032";
+}
+.glyphicon-lock:before {
+  content: "\e033";
+}
+.glyphicon-flag:before {
+  content: "\e034";
+}
+.glyphicon-headphones:before {
+  content: "\e035";
+}
+.glyphicon-volume-off:before {
+  content: "\e036";
+}
+.glyphicon-volume-down:before {
+  content: "\e037";
+}
+.glyphicon-volume-up:before {
+  content: "\e038";
+}
+.glyphicon-qrcode:before {
+  content: "\e039";
+}
+.glyphicon-barcode:before {
+  content: "\e040";
+}
+.glyphicon-tag:before {
+  content: "\e041";
+}
+.glyphicon-tags:before {
+  content: "\e042";
+}
+.glyphicon-book:before {
+  content: "\e043";
+}
+.glyphicon-bookmark:before {
+  content: "\e044";
+}
+.glyphicon-print:before {
+  content: "\e045";
+}
+.glyphicon-camera:before {
+  content: "\e046";
+}
+.glyphicon-font:before {
+  content: "\e047";
+}
+.glyphicon-bold:before {
+  content: "\e048";
+}
+.glyphicon-italic:before {
+  content: "\e049";
+}
+.glyphicon-text-height:before {
+  content: "\e050";
+}
+.glyphicon-text-width:before {
+  content: "\e051";
+}
+.glyphicon-align-left:before {
+  content: "\e052";
+}
+.glyphicon-align-center:before {
+  content: "\e053";
+}
+.glyphicon-align-right:before {
+  content: "\e054";
+}
+.glyphicon-align-justify:before {
+  content: "\e055";
+}
+.glyphicon-list:before {
+  content: "\e056";
+}
+.glyphicon-indent-left:before {
+  content: "\e057";
+}
+.glyphicon-indent-right:before {
+  content: "\e058";
+}
+.glyphicon-facetime-video:before {
+  content: "\e059";
+}
+.glyphicon-picture:before {
+  content: "\e060";
+}
+.glyphicon-map-marker:before {
+  content: "\e062";
+}
+.glyphicon-adjust:before {
+  content: "\e063";
+}
+.glyphicon-tint:before {
+  content: "\e064";
+}
+.glyphicon-edit:before {
+  content: "\e065";
+}
+.glyphicon-share:before {
+  content: "\e066";
+}
+.glyphicon-check:before {
+  content: "\e067";
+}
+.glyphicon-move:before {
+  content: "\e068";
+}
+.glyphicon-step-backward:before {
+  content: "\e069";
+}
+.glyphicon-fast-backward:before {
+  content: "\e070";
+}
+.glyphicon-backward:before {
+  content: "\e071";
+}
+.glyphicon-play:before {
+  content: "\e072";
+}
+.glyphicon-pause:before {
+  content: "\e073";
+}
+.glyphicon-stop:before {
+  content: "\e074";
+}
+.glyphicon-forward:before {
+  content: "\e075";
+}
+.glyphicon-fast-forward:before {
+  content: "\e076";
+}
+.glyphicon-step-forward:before {
+  content: "\e077";
+}
+.glyphicon-eject:before {
+  content: "\e078";
+}
+.glyphicon-chevron-left:before {
+  content: "\e079";
+}
+.glyphicon-chevron-right:before {
+  content: "\e080";
+}
+.glyphicon-plus-sign:before {
+  content: "\e081";
+}
+.glyphicon-minus-sign:before {
+  content: "\e082";
+}
+.glyphicon-remove-sign:before {
+  content: "\e083";
+}
+.glyphicon-ok-sign:before {
+  content: "\e084";
+}
+.glyphicon-question-sign:before {
+  content: "\e085";
+}
+.glyphicon-info-sign:before {
+  content: "\e086";
+}
+.glyphicon-screenshot:before {
+  content: "\e087";
+}
+.glyphicon-remove-circle:before {
+  content: "\e088";
+}
+.glyphicon-ok-circle:before {
+  content: "\e089";
+}
+.glyphicon-ban-circle:before {
+  content: "\e090";
+}
+.glyphicon-arrow-left:before {
+  content: "\e091";
+}
+.glyphicon-arrow-right:before {
+  content: "\e092";
+}
+.glyphicon-arrow-up:before {
+  content: "\e093";
+}
+.glyphicon-arrow-down:before {
+  content: "\e094";
+}
+.glyphicon-share-alt:before {
+  content: "\e095";
+}
+.glyphicon-resize-full:before {
+  content: "\e096";
+}
+.glyphicon-resize-small:before {
+  content: "\e097";
+}
+.glyphicon-exclamation-sign:before {
+  content: "\e101";
+}
+.glyphicon-gift:before {
+  content: "\e102";
+}
+.glyphicon-leaf:before {
+  content: "\e103";
+}
+.glyphicon-fire:before {
+  content: "\e104";
+}
+.glyphicon-eye-open:before {
+  content: "\e105";
+}
+.glyphicon-eye-close:before {
+  content: "\e106";
+}
+.glyphicon-warning-sign:before {
+  content: "\e107";
+}
+.glyphicon-plane:before {
+  content: "\e108";
+}
+.glyphicon-calendar:before {
+  content: "\e109";
+}
+.glyphicon-random:before {
+  content: "\e110";
+}
+.glyphicon-comment:before {
+  content: "\e111";
+}
+.glyphicon-magnet:before {
+  content: "\e112";
+}
+.glyphicon-chevron-up:before {
+  content: "\e113";
+}
+.glyphicon-chevron-down:before {
+  content: "\e114";
+}
+.glyphicon-retweet:before {
+  content: "\e115";
+}
+.glyphicon-shopping-cart:before {
+  content: "\e116";
+}
+.glyphicon-folder-close:before {
+  content: "\e117";
+}
+.glyphicon-folder-open:before {
+  content: "\e118";
+}
+.glyphicon-resize-vertical:before {
+  content: "\e119";
+}
+.glyphicon-resize-horizontal:before {
+  content: "\e120";
+}
+.glyphicon-hdd:before {
+  content: "\e121";
+}
+.glyphicon-bullhorn:before {
+  content: "\e122";
+}
+.glyphicon-bell:before {
+  content: "\e123";
+}
+.glyphicon-certificate:before {
+  content: "\e124";
+}
+.glyphicon-thumbs-up:before {
+  content: "\e125";
+}
+.glyphicon-thumbs-down:before {
+  content: "\e126";
+}
+.glyphicon-hand-right:before {
+  content: "\e127";
+}
+.glyphicon-hand-left:before {
+  content: "\e128";
+}
+.glyphicon-hand-up:before {
+  content: "\e129";
+}
+.glyphicon-hand-down:before {
+  content: "\e130";
+}
+.glyphicon-circle-arrow-right:before {
+  content: "\e131";
+}
+.glyphicon-circle-arrow-left:before {
+  content: "\e132";
+}
+.glyphicon-circle-arrow-up:before {
+  content: "\e133";
+}
+.glyphicon-circle-arrow-down:before {
+  content: "\e134";
+}
+.glyphicon-globe:before {
+  content: "\e135";
+}
+.glyphicon-wrench:before {
+  content: "\e136";
+}
+.glyphicon-tasks:before {
+  content: "\e137";
+}
+.glyphicon-filter:before {
+  content: "\e138";
+}
+.glyphicon-briefcase:before {
+  content: "\e139";
+}
+.glyphicon-fullscreen:before {
+  content: "\e140";
+}
+.glyphicon-dashboard:before {
+  content: "\e141";
+}
+.glyphicon-paperclip:before {
+  content: "\e142";
+}
+.glyphicon-heart-empty:before {
+  content: "\e143";
+}
+.glyphicon-link:before {
+  content: "\e144";
+}
+.glyphicon-phone:before {
+  content: "\e145";
+}
+.glyphicon-pushpin:before {
+  content: "\e146";
+}
+.glyphicon-usd:before {
+  content: "\e148";
+}
+.glyphicon-gbp:before {
+  content: "\e149";
+}
+.glyphicon-sort:before {
+  content: "\e150";
+}
+.glyphicon-sort-by-alphabet:before {
+  content: "\e151";
+}
+.glyphicon-sort-by-alphabet-alt:before {
+  content: "\e152";
+}
+.glyphicon-sort-by-order:before {
+  content: "\e153";
+}
+.glyphicon-sort-by-order-alt:before {
+  content: "\e154";
+}
+.glyphicon-sort-by-attributes:before {
+  content: "\e155";
+}
+.glyphicon-sort-by-attributes-alt:before {
+  content: "\e156";
+}
+.glyphicon-unchecked:before {
+  content: "\e157";
+}
+.glyphicon-expand:before {
+  content: "\e158";
+}
+.glyphicon-collapse-down:before {
+  content: "\e159";
+}
+.glyphicon-collapse-up:before {
+  content: "\e160";
+}
+.glyphicon-log-in:before {
+  content: "\e161";
+}
+.glyphicon-flash:before {
+  content: "\e162";
+}
+.glyphicon-log-out:before {
+  content: "\e163";
+}
+.glyphicon-new-window:before {
+  content: "\e164";
+}
+.glyphicon-record:before {
+  content: "\e165";
+}
+.glyphicon-save:before {
+  content: "\e166";
+}
+.glyphicon-open:before {
+  content: "\e167";
+}
+.glyphicon-saved:before {
+  content: "\e168";
+}
+.glyphicon-import:before {
+  content: "\e169";
+}
+.glyphicon-export:before {
+  content: "\e170";
+}
+.glyphicon-send:before {
+  content: "\e171";
+}
+.glyphicon-floppy-disk:before {
+  content: "\e172";
+}
+.glyphicon-floppy-saved:before {
+  content: "\e173";
+}
+.glyphicon-floppy-remove:before {
+  content: "\e174";
+}
+.glyphicon-floppy-save:before {
+  content: "\e175";
+}
+.glyphicon-floppy-open:before {
+  content: "\e176";
+}
+.glyphicon-credit-card:before {
+  content: "\e177";
+}
+.glyphicon-transfer:before {
+  content: "\e178";
+}
+.glyphicon-cutlery:before {
+  content: "\e179";
+}
+.glyphicon-header:before {
+  content: "\e180";
+}
+.glyphicon-compressed:before {
+  content: "\e181";
+}
+.glyphicon-earphone:before {
+  content: "\e182";
+}
+.glyphicon-phone-alt:before {
+  content: "\e183";
+}
+.glyphicon-tower:before {
+  content: "\e184";
+}
+.glyphicon-stats:before {
+  content: "\e185";
+}
+.glyphicon-sd-video:before {
+  content: "\e186";
+}
+.glyphicon-hd-video:before {
+  content: "\e187";
+}
+.glyphicon-subtitles:before {
+  content: "\e188";
+}
+.glyphicon-sound-stereo:before {
+  content: "\e189";
+}
+.glyphicon-sound-dolby:before {
+  content: "\e190";
+}
+.glyphicon-sound-5-1:before {
+  content: "\e191";
+}
+.glyphicon-sound-6-1:before {
+  content: "\e192";
+}
+.glyphicon-sound-7-1:before {
+  content: "\e193";
+}
+.glyphicon-copyright-mark:before {
+  content: "\e194";
+}
+.glyphicon-registration-mark:before {
+  content: "\e195";
+}
+.glyphicon-cloud-download:before {
+  content: "\e197";
+}
+.glyphicon-cloud-upload:before {
+  content: "\e198";
+}
+.glyphicon-tree-conifer:before {
+  content: "\e199";
+}
+.glyphicon-tree-deciduous:before {
+  content: "\e200";
+}
+.glyphicon-cd:before {
+  content: "\e201";
+}
+.glyphicon-save-file:before {
+  content: "\e202";
+}
+.glyphicon-open-file:before {
+  content: "\e203";
+}
+.glyphicon-level-up:before {
+  content: "\e204";
+}
+.glyphicon-copy:before {
+  content: "\e205";
+}
+.glyphicon-paste:before {
+  content: "\e206";
+}
+.glyphicon-alert:before {
+  content: "\e209";
+}
+.glyphicon-equalizer:before {
+  content: "\e210";
+}
+.glyphicon-king:before {
+  content: "\e211";
+}
+.glyphicon-queen:before {
+  content: "\e212";
+}
+.glyphicon-pawn:before {
+  content: "\e213";
+}
+.glyphicon-bishop:before {
+  content: "\e214";
+}
+.glyphicon-knight:before {
+  content: "\e215";
+}
+.glyphicon-baby-formula:before {
+  content: "\e216";
+}
+.glyphicon-tent:before {
+  content: "\26fa";
+}
+.glyphicon-blackboard:before {
+  content: "\e218";
+}
+.glyphicon-bed:before {
+  content: "\e219";
+}
+.glyphicon-apple:before {
+  content: "\f8ff";
+}
+.glyphicon-erase:before {
+  content: "\e221";
+}
+.glyphicon-hourglass:before {
+  content: "\231b";
+}
+.glyphicon-lamp:before {
+  content: "\e223";
+}
+.glyphicon-duplicate:before {
+  content: "\e224";
+}
+.glyphicon-piggy-bank:before {
+  content: "\e225";
+}
+.glyphicon-scissors:before {
+  content: "\e226";
+}
+.glyphicon-bitcoin:before {
+  content: "\e227";
+}
+.glyphicon-btc:before {
+  content: "\e227";
+}
+.glyphicon-xbt:before {
+  content: "\e227";
+}
+.glyphicon-yen:before {
+  content: "\00a5";
+}
+.glyphicon-jpy:before {
+  content: "\00a5";
+}
+.glyphicon-ruble:before {
+  content: "\20bd";
+}
+.glyphicon-rub:before {
+  content: "\20bd";
+}
+.glyphicon-scale:before {
+  content: "\e230";
+}
+.glyphicon-ice-lolly:before {
+  content: "\e231";
+}
+.glyphicon-ice-lolly-tasted:before {
+  content: "\e232";
+}
+.glyphicon-education:before {
+  content: "\e233";
+}
+.glyphicon-option-horizontal:before {
+  content: "\e234";
+}
+.glyphicon-option-vertical:before {
+  content: "\e235";
+}
+.glyphicon-menu-hamburger:before {
+  content: "\e236";
+}
+.glyphicon-modal-window:before {
+  content: "\e237";
+}
+.glyphicon-oil:before {
+  content: "\e238";
+}
+.glyphicon-grain:before {
+  content: "\e239";
+}
+.glyphicon-sunglasses:before {
+  content: "\e240";
+}
+.glyphicon-text-size:before {
+  content: "\e241";
+}
+.glyphicon-text-color:before {
+  content: "\e242";
+}
+.glyphicon-text-background:before {
+  content: "\e243";
+}
+.glyphicon-object-align-top:before {
+  content: "\e244";
+}
+.glyphicon-object-align-bottom:before {
+  content: "\e245";
+}
+.glyphicon-object-align-horizontal:before {
+  content: "\e246";
+}
+.glyphicon-object-align-left:before {
+  content: "\e247";
+}
+.glyphicon-object-align-vertical:before {
+  content: "\e248";
+}
+.glyphicon-object-align-right:before {
+  content: "\e249";
+}
+.glyphicon-triangle-right:before {
+  content: "\e250";
+}
+.glyphicon-triangle-left:before {
+  content: "\e251";
+}
+.glyphicon-triangle-bottom:before {
+  content: "\e252";
+}
+.glyphicon-triangle-top:before {
+  content: "\e253";
+}
+.glyphicon-console:before {
+  content: "\e254";
+}
+.glyphicon-superscript:before {
+  content: "\e255";
+}
+.glyphicon-subscript:before {
+  content: "\e256";
+}
+.glyphicon-menu-left:before {
+  content: "\e257";
+}
+.glyphicon-menu-right:before {
+  content: "\e258";
+}
+.glyphicon-menu-down:before {
+  content: "\e259";
+}
+.glyphicon-menu-up:before {
+  content: "\e260";
+}
+* {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+html {
+  font-size: 10px;
+
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #333;
+  background-color: #fff;
+}
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+a {
+  color: #337ab7;
+  text-decoration: none;
+}
+a:hover,
+a:focus {
+  color: #23527c;
+  text-decoration: underline;
+}
+a:focus {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+figure {
+  margin: 0;
+}
+img {
+  vertical-align: middle;
+}
+.img-responsive,
+.thumbnail > img,
+.thumbnail a > img,
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+.img-rounded {
+  border-radius: 6px;
+}
+.img-thumbnail {
+  display: inline-block;
+  max-width: 100%;
+  height: auto;
+  padding: 4px;
+  line-height: 1.42857143;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  -webkit-transition: all .2s ease-in-out;
+       -o-transition: all .2s ease-in-out;
+          transition: all .2s ease-in-out;
+}
+.img-circle {
+  border-radius: 50%;
+}
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eee;
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
+}
+[role="button"] {
+  cursor: pointer;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: inherit;
+  font-weight: 500;
+  line-height: 1.1;
+  color: inherit;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+  font-weight: normal;
+  line-height: 1;
+  color: #777;
+}
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+h1 small,
+.h1 small,
+h2 small,
+.h2 small,
+h3 small,
+.h3 small,
+h1 .small,
+.h1 .small,
+h2 .small,
+.h2 .small,
+h3 .small,
+.h3 .small {
+  font-size: 65%;
+}
+h4,
+.h4,
+h5,
+.h5,
+h6,
+.h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+h4 small,
+.h4 small,
+h5 small,
+.h5 small,
+h6 small,
+.h6 small,
+h4 .small,
+.h4 .small,
+h5 .small,
+.h5 .small,
+h6 .small,
+.h6 .small {
+  font-size: 75%;
+}
+h1,
+.h1 {
+  font-size: 36px;
+}
+h2,
+.h2 {
+  font-size: 30px;
+}
+h3,
+.h3 {
+  font-size: 24px;
+}
+h4,
+.h4 {
+  font-size: 18px;
+}
+h5,
+.h5 {
+  font-size: 14px;
+}
+h6,
+.h6 {
+  font-size: 12px;
+}
+p {
+  margin: 0 0 10px;
+}
+.lead {
+  margin-bottom: 20px;
+  font-size: 16px;
+  font-weight: 300;
+  line-height: 1.4;
+}
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+small,
+.small {
+  font-size: 85%;
+}
+mark,
+.mark {
+  padding: .2em;
+  background-color: #fcf8e3;
+}
+.text-left {
+  text-align: left;
+}
+.text-right {
+  text-align: right;
+}
+.text-center {
+  text-align: center;
+}
+.text-justify {
+  text-align: justify;
+}
+.text-nowrap {
+  white-space: nowrap;
+}
+.text-lowercase {
+  text-transform: lowercase;
+}
+.text-uppercase {
+  text-transform: uppercase;
+}
+.text-capitalize {
+  text-transform: capitalize;
+}
+.text-muted {
+  color: #777;
+}
+.text-primary {
+  color: #337ab7;
+}
+a.text-primary:hover,
+a.text-primary:focus {
+  color: #286090;
+}
+.text-success {
+  color: #3c763d;
+}
+a.text-success:hover,
+a.text-success:focus {
+  color: #2b542c;
+}
+.text-info {
+  color: #31708f;
+}
+a.text-info:hover,
+a.text-info:focus {
+  color: #245269;
+}
+.text-warning {
+  color: #8a6d3b;
+}
+a.text-warning:hover,
+a.text-warning:focus {
+  color: #66512c;
+}
+.text-danger {
+  color: #a94442;
+}
+a.text-danger:hover,
+a.text-danger:focus {
+  color: #843534;
+}
+.bg-primary {
+  color: #fff;
+  background-color: #337ab7;
+}
+a.bg-primary:hover,
+a.bg-primary:focus {
+  background-color: #286090;
+}
+.bg-success {
+  background-color: #dff0d8;
+}
+a.bg-success:hover,
+a.bg-success:focus {
+  background-color: #c1e2b3;
+}
+.bg-info {
+  background-color: #d9edf7;
+}
+a.bg-info:hover,
+a.bg-info:focus {
+  background-color: #afd9ee;
+}
+.bg-warning {
+  background-color: #fcf8e3;
+}
+a.bg-warning:hover,
+a.bg-warning:focus {
+  background-color: #f7ecb5;
+}
+.bg-danger {
+  background-color: #f2dede;
+}
+a.bg-danger:hover,
+a.bg-danger:focus {
+  background-color: #e4b9b9;
+}
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eee;
+}
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+.list-inline {
+  padding-left: 0;
+  margin-left: -5px;
+  list-style: none;
+}
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+dl {
+  margin-top: 0;
+  margin-bottom: 20px;
+}
+dt,
+dd {
+  line-height: 1.42857143;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 0;
+}
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    overflow: hidden;
+    clear: left;
+    text-align: right;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+}
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #777;
+}
+.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  font-size: 17.5px;
+  border-left: 5px solid #eee;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+  margin-bottom: 0;
+}
+blockquote footer,
+blockquote small,
+blockquote .small {
+  display: block;
+  font-size: 80%;
+  line-height: 1.42857143;
+  color: #777;
+}
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+  content: '\2014 \00A0';
+}
+.blockquote-reverse,
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  text-align: right;
+  border-right: 5px solid #eee;
+  border-left: 0;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+  content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+  content: '\00A0 \2014';
+}
+address {
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.42857143;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+kbd {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #fff;
+  background-color: #333;
+  border-radius: 3px;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+}
+kbd kbd {
+  padding: 0;
+  font-size: 100%;
+  font-weight: bold;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.42857143;
+  color: #333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border-radius: 0;
+}
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+.container {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+@media (min-width: 768px) {
+  .container {
+    width: 750px;
+  }
+}
+@media (min-width: 992px) {
+  .container {
+    width: 970px;
+  }
+}
+@media (min-width: 1200px) {
+  .container {
+    width: 1170px;
+  }
+}
+.container-fluid {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+.row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+  float: left;
+}
+.col-xs-12 {
+  width: 100%;
+}
+.col-xs-11 {
+  width: 91.66666667%;
+}
+.col-xs-10 {
+  width: 83.33333333%;
+}
+.col-xs-9 {
+  width: 75%;
+}
+.col-xs-8 {
+  width: 66.66666667%;
+}
+.col-xs-7 {
+  width: 58.33333333%;
+}
+.col-xs-6 {
+  width: 50%;
+}
+.col-xs-5 {
+  width: 41.66666667%;
+}
+.col-xs-4 {
+  width: 33.33333333%;
+}
+.col-xs-3 {
+  width: 25%;
+}
+.col-xs-2 {
+  width: 16.66666667%;
+}
+.col-xs-1 {
+  width: 8.33333333%;
+}
+.col-xs-pull-12 {
+  right: 100%;
+}
+.col-xs-pull-11 {
+  right: 91.66666667%;
+}
+.col-xs-pull-10 {
+  right: 83.33333333%;
+}
+.col-xs-pull-9 {
+  right: 75%;
+}
+.col-xs-pull-8 {
+  right: 66.66666667%;
+}
+.col-xs-pull-7 {
+  right: 58.33333333%;
+}
+.col-xs-pull-6 {
+  right: 50%;
+}
+.col-xs-pull-5 {
+  right: 41.66666667%;
+}
+.col-xs-pull-4 {
+  right: 33.33333333%;
+}
+.col-xs-pull-3 {
+  right: 25%;
+}
+.col-xs-pull-2 {
+  right: 16.66666667%;
+}
+.col-xs-pull-1 {
+  right: 8.33333333%;
+}
+.col-xs-pull-0 {
+  right: auto;
+}
+.col-xs-push-12 {
+  left: 100%;
+}
+.col-xs-push-11 {
+  left: 91.66666667%;
+}
+.col-xs-push-10 {
+  left: 83.33333333%;
+}
+.col-xs-push-9 {
+  left: 75%;
+}
+.col-xs-push-8 {
+  left: 66.66666667%;
+}
+.col-xs-push-7 {
+  left: 58.33333333%;
+}
+.col-xs-push-6 {
+  left: 50%;
+}
+.col-xs-push-5 {
+  left: 41.66666667%;
+}
+.col-xs-push-4 {
+  left: 33.33333333%;
+}
+.col-xs-push-3 {
+  left: 25%;
+}
+.col-xs-push-2 {
+  left: 16.66666667%;
+}
+.col-xs-push-1 {
+  left: 8.33333333%;
+}
+.col-xs-push-0 {
+  left: auto;
+}
+.col-xs-offset-12 {
+  margin-left: 100%;
+}
+.col-xs-offset-11 {
+  margin-left: 91.66666667%;
+}
+.col-xs-offset-10 {
+  margin-left: 83.33333333%;
+}
+.col-xs-offset-9 {
+  margin-left: 75%;
+}
+.col-xs-offset-8 {
+  margin-left: 66.66666667%;
+}
+.col-xs-offset-7 {
+  margin-left: 58.33333333%;
+}
+.col-xs-offset-6 {
+  margin-left: 50%;
+}
+.col-xs-offset-5 {
+  margin-left: 41.66666667%;
+}
+.col-xs-offset-4 {
+  margin-left: 33.33333333%;
+}
+.col-xs-offset-3 {
+  margin-left: 25%;
+}
+.col-xs-offset-2 {
+  margin-left: 16.66666667%;
+}
+.col-xs-offset-1 {
+  margin-left: 8.33333333%;
+}
+.col-xs-offset-0 {
+  margin-left: 0;
+}
+@media (min-width: 768px) {
+  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+    float: left;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-11 {
+    width: 91.66666667%;
+  }
+  .col-sm-10 {
+    width: 83.33333333%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-8 {
+    width: 66.66666667%;
+  }
+  .col-sm-7 {
+    width: 58.33333333%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-5 {
+    width: 41.66666667%;
+  }
+  .col-sm-4 {
+    width: 33.33333333%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-2 {
+    width: 16.66666667%;
+  }
+  .col-sm-1 {
+    width: 8.33333333%;
+  }
+  .col-sm-pull-12 {
+    right: 100%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-sm-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-sm-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-sm-pull-0 {
+    right: auto;
+  }
+  .col-sm-push-12 {
+    left: 100%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666667%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666667%;
+  }
+  .col-sm-push-7 {
+    left: 58.33333333%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666667%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-2 {
+    left: 16.66666667%;
+  }
+  .col-sm-push-1 {
+    left: 8.33333333%;
+  }
+  .col-sm-push-0 {
+    left: auto;
+  }
+  .col-sm-offset-12 {
+    margin-left: 100%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-sm-offset-0 {
+    margin-left: 0;
+  }
+}
+@media (min-width: 992px) {
+  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+    float: left;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-11 {
+    width: 91.66666667%;
+  }
+  .col-md-10 {
+    width: 83.33333333%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-8 {
+    width: 66.66666667%;
+  }
+  .col-md-7 {
+    width: 58.33333333%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-5 {
+    width: 41.66666667%;
+  }
+  .col-md-4 {
+    width: 33.33333333%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-2 {
+    width: 16.66666667%;
+  }
+  .col-md-1 {
+    width: 8.33333333%;
+  }
+  .col-md-pull-12 {
+    right: 100%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-md-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-md-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-md-pull-0 {
+    right: auto;
+  }
+  .col-md-push-12 {
+    left: 100%;
+  }
+  .col-md-push-11 {
+    left: 91.66666667%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-8 {
+    left: 66.66666667%;
+  }
+  .col-md-push-7 {
+    left: 58.33333333%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-5 {
+    left: 41.66666667%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-2 {
+    left: 16.66666667%;
+  }
+  .col-md-push-1 {
+    left: 8.33333333%;
+  }
+  .col-md-push-0 {
+    left: auto;
+  }
+  .col-md-offset-12 {
+    margin-left: 100%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0;
+  }
+}
+@media (min-width: 1200px) {
+  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+    float: left;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-11 {
+    width: 91.66666667%;
+  }
+  .col-lg-10 {
+    width: 83.33333333%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-8 {
+    width: 66.66666667%;
+  }
+  .col-lg-7 {
+    width: 58.33333333%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-5 {
+    width: 41.66666667%;
+  }
+  .col-lg-4 {
+    width: 33.33333333%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-2 {
+    width: 16.66666667%;
+  }
+  .col-lg-1 {
+    width: 8.33333333%;
+  }
+  .col-lg-pull-12 {
+    right: 100%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-lg-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-lg-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-lg-pull-0 {
+    right: auto;
+  }
+  .col-lg-push-12 {
+    left: 100%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666667%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666667%;
+  }
+  .col-lg-push-7 {
+    left: 58.33333333%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666667%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-2 {
+    left: 16.66666667%;
+  }
+  .col-lg-push-1 {
+    left: 8.33333333%;
+  }
+  .col-lg-push-0 {
+    left: auto;
+  }
+  .col-lg-offset-12 {
+    margin-left: 100%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0;
+  }
+}
+table {
+  background-color: transparent;
+}
+caption {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  color: #777;
+  text-align: left;
+}
+th {
+  text-align: left;
+}
+.table {
+  width: 100%;
+  max-width: 100%;
+  margin-bottom: 20px;
+}
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.42857143;
+  vertical-align: top;
+  border-top: 1px solid #ddd;
+}
+.table > thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #ddd;
+}
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+  border-top: 0;
+}
+.table > tbody + tbody {
+  border-top: 2px solid #ddd;
+}
+.table .table {
+  background-color: #fff;
+}
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+  padding: 5px;
+}
+.table-bordered {
+  border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+.table-striped > tbody > tr:nth-of-type(odd) {
+  background-color: #f9f9f9;
+}
+.table-hover > tbody > tr:hover {
+  background-color: #f5f5f5;
+}
+table col[class*="col-"] {
+  position: static;
+  display: table-column;
+  float: none;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+  position: static;
+  display: table-cell;
+  float: none;
+}
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+.table-hover > tbody > tr > td.active:hover,
+.table-hover > tbody > tr > th.active:hover,
+.table-hover > tbody > tr.active:hover > td,
+.table-hover > tbody > tr:hover > .active,
+.table-hover > tbody > tr.active:hover > th {
+  background-color: #e8e8e8;
+}
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+}
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr:hover > .success,
+.table-hover > tbody > tr.success:hover > th {
+  background-color: #d0e9c6;
+}
+.table > thead > tr > td.info,
+.table > tbody > tr > td.info,
+.table > tfoot > tr > td.info,
+.table > thead > tr > th.info,
+.table > tbody > tr > th.info,
+.table > tfoot > tr > th.info,
+.table > thead > tr.info > td,
+.table > tbody > tr.info > td,
+.table > tfoot > tr.info > td,
+.table > thead > tr.info > th,
+.table > tbody > tr.info > th,
+.table > tfoot > tr.info > th {
+  background-color: #d9edf7;
+}
+.table-hover > tbody > tr > td.info:hover,
+.table-hover > tbody > tr > th.info:hover,
+.table-hover > tbody > tr.info:hover > td,
+.table-hover > tbody > tr:hover > .info,
+.table-hover > tbody > tr.info:hover > th {
+  background-color: #c4e3f3;
+}
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+}
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr:hover > .warning,
+.table-hover > tbody > tr.warning:hover > th {
+  background-color: #faf2cc;
+}
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+}
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr:hover > .danger,
+.table-hover > tbody > tr.danger:hover > th {
+  background-color: #ebcccc;
+}
+.table-responsive {
+  min-height: .01%;
+  overflow-x: auto;
+}
+@media screen and (max-width: 767px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-y: hidden;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    border: 1px solid #ddd;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+label {
+  display: inline-block;
+  max-width: 100%;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  line-height: normal;
+}
+input[type="file"] {
+  display: block;
+}
+input[type="range"] {
+  display: block;
+  width: 100%;
+}
+select[multiple],
+select[size] {
+  height: auto;
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+output {
+  display: block;
+  padding-top: 7px;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #555;
+}
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #555;
+  background-color: #fff;
+  background-image: none;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
+       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+}
+.form-control::-moz-placeholder {
+  color: #999;
+  opacity: 1;
+}
+.form-control:-ms-input-placeholder {
+  color: #999;
+}
+.form-control::-webkit-input-placeholder {
+  color: #999;
+}
+.form-control::-ms-expand {
+  background-color: transparent;
+  border: 0;
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  background-color: #eee;
+  opacity: 1;
+}
+.form-control[disabled],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+}
+textarea.form-control {
+  height: auto;
+}
+input[type="search"] {
+  -webkit-appearance: none;
+}
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+  input[type="date"].form-control,
+  input[type="time"].form-control,
+  input[type="datetime-local"].form-control,
+  input[type="month"].form-control {
+    line-height: 34px;
+  }
+  input[type="date"].input-sm,
+  input[type="time"].input-sm,
+  input[type="datetime-local"].input-sm,
+  input[type="month"].input-sm,
+  .input-group-sm input[type="date"],
+  .input-group-sm input[type="time"],
+  .input-group-sm input[type="datetime-local"],
+  .input-group-sm input[type="month"] {
+    line-height: 30px;
+  }
+  input[type="date"].input-lg,
+  input[type="time"].input-lg,
+  input[type="datetime-local"].input-lg,
+  input[type="month"].input-lg,
+  .input-group-lg input[type="date"],
+  .input-group-lg input[type="time"],
+  .input-group-lg input[type="datetime-local"],
+  .input-group-lg input[type="month"] {
+    line-height: 46px;
+  }
+}
+.form-group {
+  margin-bottom: 15px;
+}
+.radio,
+.checkbox {
+  position: relative;
+  display: block;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.radio label,
+.checkbox label {
+  min-height: 20px;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  position: absolute;
+  margin-top: 4px \9;
+  margin-left: -20px;
+}
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+.radio-inline,
+.checkbox-inline {
+  position: relative;
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"].disabled,
+input[type="checkbox"].disabled,
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"] {
+  cursor: not-allowed;
+}
+.radio-inline.disabled,
+.checkbox-inline.disabled,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+.radio.disabled label,
+.checkbox.disabled label,
+fieldset[disabled] .radio label,
+fieldset[disabled] .checkbox label {
+  cursor: not-allowed;
+}
+.form-control-static {
+  min-height: 34px;
+  padding-top: 7px;
+  padding-bottom: 7px;
+  margin-bottom: 0;
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+  padding-right: 0;
+  padding-left: 0;
+}
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-sm,
+select[multiple].input-sm {
+  height: auto;
+}
+.form-group-sm .form-control {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.form-group-sm select.form-control {
+  height: 30px;
+  line-height: 30px;
+}
+.form-group-sm textarea.form-control,
+.form-group-sm select[multiple].form-control {
+  height: auto;
+}
+.form-group-sm .form-control-static {
+  height: 30px;
+  min-height: 32px;
+  padding: 6px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.input-lg {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+select.input-lg {
+  height: 46px;
+  line-height: 46px;
+}
+textarea.input-lg,
+select[multiple].input-lg {
+  height: auto;
+}
+.form-group-lg .form-control {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+.form-group-lg select.form-control {
+  height: 46px;
+  line-height: 46px;
+}
+.form-group-lg textarea.form-control,
+.form-group-lg select[multiple].form-control {
+  height: auto;
+}
+.form-group-lg .form-control-static {
+  height: 46px;
+  min-height: 38px;
+  padding: 11px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+}
+.has-feedback {
+  position: relative;
+}
+.has-feedback .form-control {
+  padding-right: 42.5px;
+}
+.form-control-feedback {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2;
+  display: block;
+  width: 34px;
+  height: 34px;
+  line-height: 34px;
+  text-align: center;
+  pointer-events: none;
+}
+.input-lg + .form-control-feedback,
+.input-group-lg + .form-control-feedback,
+.form-group-lg .form-control + .form-control-feedback {
+  width: 46px;
+  height: 46px;
+  line-height: 46px;
+}
+.input-sm + .form-control-feedback,
+.input-group-sm + .form-control-feedback,
+.form-group-sm .form-control + .form-control-feedback {
+  width: 30px;
+  height: 30px;
+  line-height: 30px;
+}
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline,
+.has-success.radio label,
+.has-success.checkbox label,
+.has-success.radio-inline label,
+.has-success.checkbox-inline label {
+  color: #3c763d;
+}
+.has-success .form-control {
+  border-color: #3c763d;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-success .form-control:focus {
+  border-color: #2b542c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+}
+.has-success .input-group-addon {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #3c763d;
+}
+.has-success .form-control-feedback {
+  color: #3c763d;
+}
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline,
+.has-warning.radio label,
+.has-warning.checkbox label,
+.has-warning.radio-inline label,
+.has-warning.checkbox-inline label {
+  color: #8a6d3b;
+}
+.has-warning .form-control {
+  border-color: #8a6d3b;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-warning .form-control:focus {
+  border-color: #66512c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+}
+.has-warning .input-group-addon {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #8a6d3b;
+}
+.has-warning .form-control-feedback {
+  color: #8a6d3b;
+}
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline,
+.has-error.radio label,
+.has-error.checkbox label,
+.has-error.radio-inline label,
+.has-error.checkbox-inline label {
+  color: #a94442;
+}
+.has-error .form-control {
+  border-color: #a94442;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-error .form-control:focus {
+  border-color: #843534;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+}
+.has-error .input-group-addon {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #a94442;
+}
+.has-error .form-control-feedback {
+  color: #a94442;
+}
+.has-feedback label ~ .form-control-feedback {
+  top: 25px;
+}
+.has-feedback label.sr-only ~ .form-control-feedback {
+  top: 0;
+}
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .form-inline .form-control-static {
+    display: inline-block;
+  }
+  .form-inline .input-group {
+    display: inline-table;
+    vertical-align: middle;
+  }
+  .form-inline .input-group .input-group-addon,
+  .form-inline .input-group .input-group-btn,
+  .form-inline .input-group .form-control {
+    width: auto;
+  }
+  .form-inline .input-group > .form-control {
+    width: 100%;
+  }
+  .form-inline .control-label {
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .radio label,
+  .form-inline .checkbox label {
+    padding-left: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    position: relative;
+    margin-left: 0;
+  }
+  .form-inline .has-feedback .form-control-feedback {
+    top: 0;
+  }
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 7px;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox {
+  min-height: 27px;
+}
+.form-horizontal .form-group {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    padding-top: 7px;
+    margin-bottom: 0;
+    text-align: right;
+  }
+}
+.form-horizontal .has-feedback .form-control-feedback {
+  right: 15px;
+}
+@media (min-width: 768px) {
+  .form-horizontal .form-group-lg .control-label {
+    padding-top: 11px;
+    font-size: 18px;
+  }
+}
+@media (min-width: 768px) {
+  .form-horizontal .form-group-sm .control-label {
+    padding-top: 6px;
+    font-size: 12px;
+  }
+}
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  -ms-touch-action: manipulation;
+      touch-action: manipulation;
+  cursor: pointer;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.btn:focus,
+.btn:active:focus,
+.btn.active:focus,
+.btn.focus,
+.btn:active.focus,
+.btn.active.focus {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.btn:hover,
+.btn:focus,
+.btn.focus {
+  color: #333;
+  text-decoration: none;
+}
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  cursor: not-allowed;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+  opacity: .65;
+}
+a.btn.disabled,
+fieldset[disabled] a.btn {
+  pointer-events: none;
+}
+.btn-default {
+  color: #333;
+  background-color: #fff;
+  border-color: #ccc;
+}
+.btn-default:focus,
+.btn-default.focus {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #8c8c8c;
+}
+.btn-default:hover {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #adadad;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+  color: #333;
+  background-color: #e6e6e6;
+  border-color: #adadad;
+}
+.btn-default:active:hover,
+.btn-default.active:hover,
+.open > .dropdown-toggle.btn-default:hover,
+.btn-default:active:focus,
+.btn-default.active:focus,
+.open > .dropdown-toggle.btn-default:focus,
+.btn-default:active.focus,
+.btn-default.active.focus,
+.open > .dropdown-toggle.btn-default.focus {
+  color: #333;
+  background-color: #d4d4d4;
+  border-color: #8c8c8c;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+  background-image: none;
+}
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled.focus,
+.btn-default[disabled].focus,
+fieldset[disabled] .btn-default.focus {
+  background-color: #fff;
+  border-color: #ccc;
+}
+.btn-default .badge {
+  color: #fff;
+  background-color: #333;
+}
+.btn-primary {
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #2e6da4;
+}
+.btn-primary:focus,
+.btn-primary.focus {
+  color: #fff;
+  background-color: #286090;
+  border-color: #122b40;
+}
+.btn-primary:hover {
+  color: #fff;
+  background-color: #286090;
+  border-color: #204d74;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+  color: #fff;
+  background-color: #286090;
+  border-color: #204d74;
+}
+.btn-primary:active:hover,
+.btn-primary.active:hover,
+.open > .dropdown-toggle.btn-primary:hover,
+.btn-primary:active:focus,
+.btn-primary.active:focus,
+.open > .dropdown-toggle.btn-primary:focus,
+.btn-primary:active.focus,
+.btn-primary.active.focus,
+.open > .dropdown-toggle.btn-primary.focus {
+  color: #fff;
+  background-color: #204d74;
+  border-color: #122b40;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled.focus,
+.btn-primary[disabled].focus,
+fieldset[disabled] .btn-primary.focus {
+  background-color: #337ab7;
+  border-color: #2e6da4;
+}
+.btn-primary .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.btn-success {
+  color: #fff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-success:focus,
+.btn-success.focus {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #255625;
+}
+.btn-success:hover {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #398439;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+  color: #fff;
+  background-color: #449d44;
+  border-color: #398439;
+}
+.btn-success:active:hover,
+.btn-success.active:hover,
+.open > .dropdown-toggle.btn-success:hover,
+.btn-success:active:focus,
+.btn-success.active:focus,
+.open > .dropdown-toggle.btn-success:focus,
+.btn-success:active.focus,
+.btn-success.active.focus,
+.open > .dropdown-toggle.btn-success.focus {
+  color: #fff;
+  background-color: #398439;
+  border-color: #255625;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+  background-image: none;
+}
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled.focus,
+.btn-success[disabled].focus,
+fieldset[disabled] .btn-success.focus {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+.btn-success .badge {
+  color: #5cb85c;
+  background-color: #fff;
+}
+.btn-info {
+  color: #fff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-info:focus,
+.btn-info.focus {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #1b6d85;
+}
+.btn-info:hover {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #269abc;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+  color: #fff;
+  background-color: #31b0d5;
+  border-color: #269abc;
+}
+.btn-info:active:hover,
+.btn-info.active:hover,
+.open > .dropdown-toggle.btn-info:hover,
+.btn-info:active:focus,
+.btn-info.active:focus,
+.open > .dropdown-toggle.btn-info:focus,
+.btn-info:active.focus,
+.btn-info.active.focus,
+.open > .dropdown-toggle.btn-info.focus {
+  color: #fff;
+  background-color: #269abc;
+  border-color: #1b6d85;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+  background-image: none;
+}
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled.focus,
+.btn-info[disabled].focus,
+fieldset[disabled] .btn-info.focus {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+.btn-info .badge {
+  color: #5bc0de;
+  background-color: #fff;
+}
+.btn-warning {
+  color: #fff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-warning:focus,
+.btn-warning.focus {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #985f0d;
+}
+.btn-warning:hover {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #d58512;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+  color: #fff;
+  background-color: #ec971f;
+  border-color: #d58512;
+}
+.btn-warning:active:hover,
+.btn-warning.active:hover,
+.open > .dropdown-toggle.btn-warning:hover,
+.btn-warning:active:focus,
+.btn-warning.active:focus,
+.open > .dropdown-toggle.btn-warning:focus,
+.btn-warning:active.focus,
+.btn-warning.active.focus,
+.open > .dropdown-toggle.btn-warning.focus {
+  color: #fff;
+  background-color: #d58512;
+  border-color: #985f0d;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled.focus,
+.btn-warning[disabled].focus,
+fieldset[disabled] .btn-warning.focus {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+.btn-warning .badge {
+  color: #f0ad4e;
+  background-color: #fff;
+}
+.btn-danger {
+  color: #fff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-danger:focus,
+.btn-danger.focus {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #761c19;
+}
+.btn-danger:hover {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #ac2925;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+  color: #fff;
+  background-color: #c9302c;
+  border-color: #ac2925;
+}
+.btn-danger:active:hover,
+.btn-danger.active:hover,
+.open > .dropdown-toggle.btn-danger:hover,
+.btn-danger:active:focus,
+.btn-danger.active:focus,
+.open > .dropdown-toggle.btn-danger:focus,
+.btn-danger:active.focus,
+.btn-danger.active.focus,
+.open > .dropdown-toggle.btn-danger.focus {
+  color: #fff;
+  background-color: #ac2925;
+  border-color: #761c19;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled.focus,
+.btn-danger[disabled].focus,
+fieldset[disabled] .btn-danger.focus {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+.btn-danger .badge {
+  color: #d9534f;
+  background-color: #fff;
+}
+.btn-link {
+  font-weight: normal;
+  color: #337ab7;
+  border-radius: 0;
+}
+.btn-link,
+.btn-link:active,
+.btn-link.active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+.btn-link:hover,
+.btn-link:focus {
+  color: #23527c;
+  text-decoration: underline;
+  background-color: transparent;
+}
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #777;
+  text-decoration: none;
+}
+.btn-lg,
+.btn-group-lg > .btn {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+.btn-sm,
+.btn-group-sm > .btn {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.btn-xs,
+.btn-group-xs > .btn {
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+.btn-block {
+  display: block;
+  width: 100%;
+}
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity .15s linear;
+       -o-transition: opacity .15s linear;
+          transition: opacity .15s linear;
+}
+.fade.in {
+  opacity: 1;
+}
+.collapse {
+  display: none;
+}
+.collapse.in {
+  display: block;
+}
+tr.collapse.in {
+  display: table-row;
+}
+tbody.collapse.in {
+  display: table-row-group;
+}
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition-timing-function: ease;
+       -o-transition-timing-function: ease;
+          transition-timing-function: ease;
+  -webkit-transition-duration: .35s;
+       -o-transition-duration: .35s;
+          transition-duration: .35s;
+  -webkit-transition-property: height, visibility;
+       -o-transition-property: height, visibility;
+          transition-property: height, visibility;
+}
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px dashed;
+  border-top: 4px solid \9;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+}
+.dropup,
+.dropdown {
+  position: relative;
+}
+.dropdown-toggle:focus {
+  outline: 0;
+}
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  font-size: 14px;
+  text-align: left;
+  list-style: none;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, .15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.42857143;
+  color: #333;
+  white-space: nowrap;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #262626;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #fff;
+  text-decoration: none;
+  background-color: #337ab7;
+  outline: 0;
+}
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #777;
+}
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.open > .dropdown-menu {
+  display: block;
+}
+.open > a {
+  outline: 0;
+}
+.dropdown-menu-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu-left {
+  right: auto;
+  left: 0;
+}
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.42857143;
+  color: #777;
+  white-space: nowrap;
+}
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  content: "";
+  border-top: 0;
+  border-bottom: 4px dashed;
+  border-bottom: 4px solid \9;
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 2px;
+}
+@media (min-width: 768px) {
+  .navbar-right .dropdown-menu {
+    right: 0;
+    left: auto;
+  }
+  .navbar-right .dropdown-menu-left {
+    right: auto;
+    left: 0;
+  }
+}
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+  margin-left: -1px;
+}
+.btn-toolbar {
+  margin-left: -5px;
+}
+.btn-toolbar .btn,
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group {
+  float: left;
+}
+.btn-toolbar > .btn,
+.btn-toolbar > .btn-group,
+.btn-toolbar > .input-group {
+  margin-left: 5px;
+}
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group > .btn-group {
+  float: left;
+}
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-group.open .dropdown-toggle.btn-link {
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.btn .caret {
+  margin-left: 0;
+}
+.btn-lg .caret {
+  border-width: 5px 5px 0;
+  border-bottom-width: 0;
+}
+.dropup .btn-lg .caret {
+  border-width: 0 5px 5px;
+}
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group,
+.btn-group-vertical > .btn-group > .btn {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+.btn-group-vertical > .btn-group > .btn {
+  float: none;
+}
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+  margin-top: -1px;
+  margin-left: 0;
+}
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+  border-collapse: separate;
+}
+.btn-group-justified > .btn,
+.btn-group-justified > .btn-group {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+.btn-group-justified > .btn-group .btn {
+  width: 100%;
+}
+.btn-group-justified > .btn-group .dropdown-menu {
+  left: auto;
+}
+[data-toggle="buttons"] > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn input[type="checkbox"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {
+  position: absolute;
+  clip: rect(0, 0, 0, 0);
+  pointer-events: none;
+}
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+.input-group[class*="col-"] {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+.input-group .form-control {
+  position: relative;
+  z-index: 2;
+  float: left;
+  width: 100%;
+  margin-bottom: 0;
+}
+.input-group .form-control:focus {
+  z-index: 3;
+}
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 46px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+  border-radius: 6px;
+}
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 46px;
+  line-height: 46px;
+}
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn,
+select[multiple].input-group-lg > .form-control,
+select[multiple].input-group-lg > .input-group-addon,
+select[multiple].input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn,
+select[multiple].input-group-sm > .form-control,
+select[multiple].input-group-sm > .input-group-addon,
+select[multiple].input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  color: #555;
+  text-align: center;
+  background-color: #eee;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.input-group-addon:first-child {
+  border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child),
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.input-group-addon:last-child {
+  border-left: 0;
+}
+.input-group-btn {
+  position: relative;
+  font-size: 0;
+  white-space: nowrap;
+}
+.input-group-btn > .btn {
+  position: relative;
+}
+.input-group-btn > .btn + .btn {
+  margin-left: -1px;
+}
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:focus,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group {
+  margin-right: -1px;
+}
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group {
+  z-index: 2;
+  margin-left: -1px;
+}
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+.nav > li {
+  position: relative;
+  display: block;
+}
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eee;
+}
+.nav > li.disabled > a {
+  color: #777;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #777;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eee;
+  border-color: #337ab7;
+}
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+.nav > li > a > img {
+  max-width: none;
+}
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.42857143;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+  border-color: #eee #eee #ddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555;
+  cursor: default;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+}
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+.nav-tabs.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-tabs.nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+  border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li > a {
+    border-bottom: 1px solid #ddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs.nav-justified > .active > a,
+  .nav-tabs.nav-justified > .active > a:hover,
+  .nav-tabs.nav-justified > .active > a:focus {
+    border-bottom-color: #fff;
+  }
+}
+.nav-pills > li {
+  float: left;
+}
+.nav-pills > li > a {
+  border-radius: 4px;
+}
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #fff;
+  background-color: #337ab7;
+}
+.nav-stacked > li {
+  float: none;
+}
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+.nav-justified {
+  width: 100%;
+}
+.nav-justified > li {
+  float: none;
+}
+.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+  border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+  .nav-tabs-justified > li > a {
+    border-bottom: 1px solid #ddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs-justified > .active > a,
+  .nav-tabs-justified > .active > a:hover,
+  .nav-tabs-justified > .active > a:focus {
+    border-bottom-color: #fff;
+  }
+}
+.tab-content > .tab-pane {
+  display: none;
+}
+.tab-content > .active {
+  display: block;
+}
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.navbar {
+  position: relative;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+.navbar-collapse {
+  padding-right: 15px;
+  padding-left: 15px;
+  overflow-x: visible;
+  -webkit-overflow-scrolling: touch;
+  border-top: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+}
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: visible;
+  }
+  .navbar-fixed-top .navbar-collapse,
+  .navbar-static-top .navbar-collapse,
+  .navbar-fixed-bottom .navbar-collapse {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+.navbar-fixed-top .navbar-collapse,
+.navbar-fixed-bottom .navbar-collapse {
+  max-height: 340px;
+}
+@media (max-device-width: 480px) and (orientation: landscape) {
+  .navbar-fixed-top .navbar-collapse,
+  .navbar-fixed-bottom .navbar-collapse {
+    max-height: 200px;
+  }
+}
+.container > .navbar-header,
+.container-fluid > .navbar-header,
+.container > .navbar-collapse,
+.container-fluid > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container-fluid > .navbar-header,
+  .container > .navbar-collapse,
+  .container-fluid > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+.navbar-static-top {
+  z-index: 1000;
+  border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top {
+  top: 0;
+  border-width: 0 0 1px;
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+  border-width: 1px 0 0;
+}
+.navbar-brand {
+  float: left;
+  height: 50px;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+.navbar-brand > img {
+  display: block;
+}
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand,
+  .navbar > .container-fluid .navbar-brand {
+    margin-left: -15px;
+  }
+}
+.navbar-toggle {
+  position: relative;
+  float: right;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-right: 15px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.navbar-toggle:focus {
+  outline: 0;
+}
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+.navbar-form {
+  padding: 10px 15px;
+  margin-top: 8px;
+  margin-right: -15px;
+  margin-bottom: 8px;
+  margin-left: -15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+}
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control-static {
+    display: inline-block;
+  }
+  .navbar-form .input-group {
+    display: inline-table;
+    vertical-align: middle;
+  }
+  .navbar-form .input-group .input-group-addon,
+  .navbar-form .input-group .input-group-btn,
+  .navbar-form .input-group .form-control {
+    width: auto;
+  }
+  .navbar-form .input-group > .form-control {
+    width: 100%;
+  }
+  .navbar-form .control-label {
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .radio label,
+  .navbar-form .checkbox label {
+    padding-left: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    position: relative;
+    margin-left: 0;
+  }
+  .navbar-form .has-feedback .form-control-feedback {
+    top: 0;
+  }
+}
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+  .navbar-form .form-group:last-child {
+    margin-bottom: 0;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin-right: 0;
+    margin-left: 0;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+}
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  margin-bottom: 0;
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+.navbar-btn.btn-sm {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.navbar-btn.btn-xs {
+  margin-top: 14px;
+  margin-bottom: 14px;
+}
+.navbar-text {
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+@media (min-width: 768px) {
+  .navbar-text {
+    float: left;
+    margin-right: 15px;
+    margin-left: 15px;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+    margin-right: -15px;
+  }
+  .navbar-right ~ .navbar-right {
+    margin-right: 0;
+  }
+}
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+.navbar-default .navbar-brand {
+  color: #777;
+}
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+.navbar-default .navbar-text {
+  color: #777;
+}
+.navbar-default .navbar-nav > li > a {
+  color: #777;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333;
+  background-color: transparent;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555;
+  background-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #ccc;
+  background-color: transparent;
+}
+.navbar-default .navbar-toggle {
+  border-color: #ddd;
+}
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #ddd;
+}
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #888;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  color: #555;
+  background-color: #e7e7e7;
+}
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #ccc;
+    background-color: transparent;
+  }
+}
+.navbar-default .navbar-link {
+  color: #777;
+}
+.navbar-default .navbar-link:hover {
+  color: #333;
+}
+.navbar-default .btn-link {
+  color: #777;
+}
+.navbar-default .btn-link:hover,
+.navbar-default .btn-link:focus {
+  color: #333;
+}
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:hover,
+.navbar-default .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-default .btn-link:focus {
+  color: #ccc;
+}
+.navbar-inverse {
+  background-color: #222;
+  border-color: #080808;
+}
+.navbar-inverse .navbar-brand {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #fff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-text {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #fff;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #fff;
+  background-color: #080808;
+}
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444;
+  background-color: transparent;
+}
+.navbar-inverse .navbar-toggle {
+  border-color: #333;
+}
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333;
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #fff;
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #fff;
+  background-color: #080808;
+}
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #9d9d9d;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #fff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #fff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444;
+    background-color: transparent;
+  }
+}
+.navbar-inverse .navbar-link {
+  color: #9d9d9d;
+}
+.navbar-inverse .navbar-link:hover {
+  color: #fff;
+}
+.navbar-inverse .btn-link {
+  color: #9d9d9d;
+}
+.navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link:focus {
+  color: #fff;
+}
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-inverse .btn-link:focus {
+  color: #444;
+}
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+.breadcrumb > li {
+  display: inline-block;
+}
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #ccc;
+  content: "/\00a0";
+}
+.breadcrumb > .active {
+  color: #777;
+}
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+.pagination > li {
+  display: inline;
+}
+.pagination > li > a,
+.pagination > li > span {
+  position: relative;
+  float: left;
+  padding: 6px 12px;
+  margin-left: -1px;
+  line-height: 1.42857143;
+  color: #337ab7;
+  text-decoration: none;
+  background-color: #fff;
+  border: 1px solid #ddd;
+}
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  margin-left: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+  z-index: 2;
+  color: #23527c;
+  background-color: #eee;
+  border-color: #ddd;
+}
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  z-index: 3;
+  color: #fff;
+  cursor: default;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #fff;
+  border-color: #ddd;
+}
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.3333333;
+}
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 6px;
+}
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-top-left-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+.pager li {
+  display: inline;
+}
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 15px;
+}
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #eee;
+}
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #fff;
+}
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: #fff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+}
+a.label:hover,
+a.label:focus {
+  color: #fff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.label:empty {
+  display: none;
+}
+.btn .label {
+  position: relative;
+  top: -1px;
+}
+.label-default {
+  background-color: #777;
+}
+.label-default[href]:hover,
+.label-default[href]:focus {
+  background-color: #5e5e5e;
+}
+.label-primary {
+  background-color: #337ab7;
+}
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+  background-color: #286090;
+}
+.label-success {
+  background-color: #5cb85c;
+}
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+.label-info {
+  background-color: #5bc0de;
+}
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+.label-warning {
+  background-color: #f0ad4e;
+}
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+.label-danger {
+  background-color: #d9534f;
+}
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #fff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  background-color: #777;
+  border-radius: 10px;
+}
+.badge:empty {
+  display: none;
+}
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+.btn-xs .badge,
+.btn-group-xs > .btn .badge {
+  top: 0;
+  padding: 1px 5px;
+}
+a.badge:hover,
+a.badge:focus {
+  color: #fff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.list-group-item > .badge {
+  float: right;
+}
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+.jumbotron {
+  padding-top: 30px;
+  padding-bottom: 30px;
+  margin-bottom: 30px;
+  color: inherit;
+  background-color: #eee;
+}
+.jumbotron h1,
+.jumbotron .h1 {
+  color: inherit;
+}
+.jumbotron p {
+  margin-bottom: 15px;
+  font-size: 21px;
+  font-weight: 200;
+}
+.jumbotron > hr {
+  border-top-color: #d5d5d5;
+}
+.container .jumbotron,
+.container-fluid .jumbotron {
+  padding-right: 15px;
+  padding-left: 15px;
+  border-radius: 6px;
+}
+.jumbotron .container {
+  max-width: 100%;
+}
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron,
+  .container-fluid .jumbotron {
+    padding-right: 60px;
+    padding-left: 60px;
+  }
+  .jumbotron h1,
+  .jumbotron .h1 {
+    font-size: 63px;
+  }
+}
+.thumbnail {
+  display: block;
+  padding: 4px;
+  margin-bottom: 20px;
+  line-height: 1.42857143;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  -webkit-transition: border .2s ease-in-out;
+       -o-transition: border .2s ease-in-out;
+          transition: border .2s ease-in-out;
+}
+.thumbnail > img,
+.thumbnail a > img {
+  margin-right: auto;
+  margin-left: auto;
+}
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+  border-color: #337ab7;
+}
+.thumbnail .caption {
+  padding: 9px;
+  color: #333;
+}
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+.alert .alert-link {
+  font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+.alert > p + p {
+  margin-top: 5px;
+}
+.alert-dismissable,
+.alert-dismissible {
+  padding-right: 35px;
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+.alert-success {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+  color: #2b542c;
+}
+.alert-info {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+  color: #245269;
+}
+.alert-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+.alert-warning hr {
+  border-top-color: #f7e1b5;
+}
+.alert-warning .alert-link {
+  color: #66512c;
+}
+.alert-danger {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+.alert-danger hr {
+  border-top-color: #e4b9c0;
+}
+.alert-danger .alert-link {
+  color: #843534;
+}
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+}
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  line-height: 20px;
+  color: #fff;
+  text-align: center;
+  background-color: #337ab7;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+  -webkit-transition: width .6s ease;
+       -o-transition: width .6s ease;
+          transition: width .6s ease;
+}
+.progress-striped .progress-bar,
+.progress-bar-striped {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+          background-size: 40px 40px;
+}
+.progress.active .progress-bar,
+.progress-bar.active {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+.progress-striped .progress-bar-success {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+.progress-striped .progress-bar-info {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+.progress-striped .progress-bar-warning {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+.progress-striped .progress-bar-danger {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.media {
+  margin-top: 15px;
+}
+.media:first-child {
+  margin-top: 0;
+}
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+.media-body {
+  width: 10000px;
+}
+.media-object {
+  display: block;
+}
+.media-object.img-thumbnail {
+  max-width: none;
+}
+.media-right,
+.media > .pull-right {
+  padding-left: 10px;
+}
+.media-left,
+.media > .pull-left {
+  padding-right: 10px;
+}
+.media-left,
+.media-right,
+.media-body {
+  display: table-cell;
+  vertical-align: top;
+}
+.media-middle {
+  vertical-align: middle;
+}
+.media-bottom {
+  vertical-align: bottom;
+}
+.media-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+}
+.list-group-item:first-child {
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+}
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+a.list-group-item,
+button.list-group-item {
+  color: #555;
+}
+a.list-group-item .list-group-item-heading,
+button.list-group-item .list-group-item-heading {
+  color: #333;
+}
+a.list-group-item:hover,
+button.list-group-item:hover,
+a.list-group-item:focus,
+button.list-group-item:focus {
+  color: #555;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+button.list-group-item {
+  width: 100%;
+  text-align: left;
+}
+.list-group-item.disabled,
+.list-group-item.disabled:hover,
+.list-group-item.disabled:focus {
+  color: #777;
+  cursor: not-allowed;
+  background-color: #eee;
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading {
+  color: inherit;
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text {
+  color: #777;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  z-index: 2;
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active .list-group-item-heading > small,
+.list-group-item.active:hover .list-group-item-heading > small,
+.list-group-item.active:focus .list-group-item-heading > small,
+.list-group-item.active .list-group-item-heading > .small,
+.list-group-item.active:hover .list-group-item-heading > .small,
+.list-group-item.active:focus .list-group-item-heading > .small {
+  color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+  color: #c7ddef;
+}
+.list-group-item-success {
+  color: #3c763d;
+  background-color: #dff0d8;
+}
+a.list-group-item-success,
+button.list-group-item-success {
+  color: #3c763d;
+}
+a.list-group-item-success .list-group-item-heading,
+button.list-group-item-success .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-success:hover,
+button.list-group-item-success:hover,
+a.list-group-item-success:focus,
+button.list-group-item-success:focus {
+  color: #3c763d;
+  background-color: #d0e9c6;
+}
+a.list-group-item-success.active,
+button.list-group-item-success.active,
+a.list-group-item-success.active:hover,
+button.list-group-item-success.active:hover,
+a.list-group-item-success.active:focus,
+button.list-group-item-success.active:focus {
+  color: #fff;
+  background-color: #3c763d;
+  border-color: #3c763d;
+}
+.list-group-item-info {
+  color: #31708f;
+  background-color: #d9edf7;
+}
+a.list-group-item-info,
+button.list-group-item-info {
+  color: #31708f;
+}
+a.list-group-item-info .list-group-item-heading,
+button.list-group-item-info .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-info:hover,
+button.list-group-item-info:hover,
+a.list-group-item-info:focus,
+button.list-group-item-info:focus {
+  color: #31708f;
+  background-color: #c4e3f3;
+}
+a.list-group-item-info.active,
+button.list-group-item-info.active,
+a.list-group-item-info.active:hover,
+button.list-group-item-info.active:hover,
+a.list-group-item-info.active:focus,
+button.list-group-item-info.active:focus {
+  color: #fff;
+  background-color: #31708f;
+  border-color: #31708f;
+}
+.list-group-item-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+}
+a.list-group-item-warning,
+button.list-group-item-warning {
+  color: #8a6d3b;
+}
+a.list-group-item-warning .list-group-item-heading,
+button.list-group-item-warning .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-warning:hover,
+button.list-group-item-warning:hover,
+a.list-group-item-warning:focus,
+button.list-group-item-warning:focus {
+  color: #8a6d3b;
+  background-color: #faf2cc;
+}
+a.list-group-item-warning.active,
+button.list-group-item-warning.active,
+a.list-group-item-warning.active:hover,
+button.list-group-item-warning.active:hover,
+a.list-group-item-warning.active:focus,
+button.list-group-item-warning.active:focus {
+  color: #fff;
+  background-color: #8a6d3b;
+  border-color: #8a6d3b;
+}
+.list-group-item-danger {
+  color: #a94442;
+  background-color: #f2dede;
+}
+a.list-group-item-danger,
+button.list-group-item-danger {
+  color: #a94442;
+}
+a.list-group-item-danger .list-group-item-heading,
+button.list-group-item-danger .list-group-item-heading {
+  color: inherit;
+}
+a.list-group-item-danger:hover,
+button.list-group-item-danger:hover,
+a.list-group-item-danger:focus,
+button.list-group-item-danger:focus {
+  color: #a94442;
+  background-color: #ebcccc;
+}
+a.list-group-item-danger.active,
+button.list-group-item-danger.active,
+a.list-group-item-danger.active:hover,
+button.list-group-item-danger.active:hover,
+a.list-group-item-danger.active:focus,
+button.list-group-item-danger.active:focus {
+  color: #fff;
+  background-color: #a94442;
+  border-color: #a94442;
+}
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+.panel {
+  margin-bottom: 20px;
+  background-color: #fff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+}
+.panel-body {
+  padding: 15px;
+}
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel-heading > .dropdown .dropdown-toggle {
+  color: inherit;
+}
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+  color: inherit;
+}
+.panel-title > a,
+.panel-title > small,
+.panel-title > .small,
+.panel-title > small > a,
+.panel-title > .small > a {
+  color: inherit;
+}
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
+  margin-bottom: 0;
+}
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
+  border-width: 1px 0;
+  border-radius: 0;
+}
+.panel > .list-group:first-child .list-group-item:first-child,
+.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+  border-top: 0;
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .list-group:last-child .list-group-item:last-child,
+.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+  border-bottom: 0;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+.list-group + .panel-footer {
+  border-top-width: 0;
+}
+.panel > .table,
+.panel > .table-responsive > .table,
+.panel > .panel-collapse > .table {
+  margin-bottom: 0;
+}
+.panel > .table caption,
+.panel > .table-responsive > .table caption,
+.panel > .panel-collapse > .table caption {
+  padding-right: 15px;
+  padding-left: 15px;
+}
+.panel > .table:first-child,
+.panel > .table-responsive:first-child > .table:first-child {
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+  border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+  border-top-right-radius: 3px;
+}
+.panel > .table:last-child,
+.panel > .table-responsive:last-child > .table:last-child {
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+  border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+  border-bottom-right-radius: 3px;
+}
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive,
+.panel > .table + .panel-body,
+.panel > .table-responsive + .panel-body {
+  border-top: 1px solid #ddd;
+}
+.panel > .table > tbody:first-child > tr:first-child th,
+.panel > .table > tbody:first-child > tr:first-child td {
+  border-top: 0;
+}
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+  border: 0;
+}
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+  border-left: 0;
+}
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+  border-right: 0;
+}
+.panel > .table-bordered > thead > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
+.panel > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-bordered > thead > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
+.panel > .table-bordered > tbody > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
+  border-bottom: 0;
+}
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+  border-bottom: 0;
+}
+.panel > .table-responsive {
+  margin-bottom: 0;
+  border: 0;
+}
+.panel-group {
+  margin-bottom: 20px;
+}
+.panel-group .panel {
+  margin-bottom: 0;
+  border-radius: 4px;
+}
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse > .panel-body,
+.panel-group .panel-heading + .panel-collapse > .list-group {
+  border-top: 1px solid #ddd;
+}
+.panel-group .panel-footer {
+  border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #ddd;
+}
+.panel-default {
+  border-color: #ddd;
+}
+.panel-default > .panel-heading {
+  color: #333;
+  background-color: #f5f5f5;
+  border-color: #ddd;
+}
+.panel-default > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #ddd;
+}
+.panel-default > .panel-heading .badge {
+  color: #f5f5f5;
+  background-color: #333;
+}
+.panel-default > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #ddd;
+}
+.panel-primary {
+  border-color: #337ab7;
+}
+.panel-primary > .panel-heading {
+  color: #fff;
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.panel-primary > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #337ab7;
+}
+.panel-primary > .panel-heading .badge {
+  color: #337ab7;
+  background-color: #fff;
+}
+.panel-primary > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #337ab7;
+}
+.panel-success {
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+  color: #3c763d;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #d6e9c6;
+}
+.panel-success > .panel-heading .badge {
+  color: #dff0d8;
+  background-color: #3c763d;
+}
+.panel-success > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+.panel-info {
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #bce8f1;
+}
+.panel-info > .panel-heading .badge {
+  color: #d9edf7;
+  background-color: #31708f;
+}
+.panel-info > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #bce8f1;
+}
+.panel-warning {
+  border-color: #faebcc;
+}
+.panel-warning > .panel-heading {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+.panel-warning > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #faebcc;
+}
+.panel-warning > .panel-heading .badge {
+  color: #fcf8e3;
+  background-color: #8a6d3b;
+}
+.panel-warning > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #faebcc;
+}
+.panel-danger {
+  border-color: #ebccd1;
+}
+.panel-danger > .panel-heading {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+.panel-danger > .panel-heading + .panel-collapse > .panel-body {
+  border-top-color: #ebccd1;
+}
+.panel-danger > .panel-heading .badge {
+  color: #f2dede;
+  background-color: #a94442;
+}
+.panel-danger > .panel-footer + .panel-collapse > .panel-body {
+  border-bottom-color: #ebccd1;
+}
+.embed-responsive {
+  position: relative;
+  display: block;
+  height: 0;
+  padding: 0;
+  overflow: hidden;
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border: 0;
+}
+.embed-responsive-16by9 {
+  padding-bottom: 56.25%;
+}
+.embed-responsive-4by3 {
+  padding-bottom: 75%;
+}
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+}
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, .15);
+}
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000;
+  text-shadow: 0 1px 0 #fff;
+  filter: alpha(opacity=20);
+  opacity: .2;
+}
+.close:hover,
+.close:focus {
+  color: #000;
+  text-decoration: none;
+  cursor: pointer;
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+button.close {
+  -webkit-appearance: none;
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+}
+.modal-open {
+  overflow: hidden;
+}
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1050;
+  display: none;
+  overflow: hidden;
+  -webkit-overflow-scrolling: touch;
+  outline: 0;
+}
+.modal.fade .modal-dialog {
+  -webkit-transition: -webkit-transform .3s ease-out;
+       -o-transition:      -o-transform .3s ease-out;
+          transition:         transform .3s ease-out;
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+       -o-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+}
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+       -o-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+.modal-open .modal {
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+.modal-dialog {
+  position: relative;
+  width: auto;
+  margin: 10px;
+}
+.modal-content {
+  position: relative;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, .2);
+  border-radius: 6px;
+  outline: 0;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+}
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000;
+}
+.modal-backdrop.fade {
+  filter: alpha(opacity=0);
+  opacity: 0;
+}
+.modal-backdrop.in {
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+.modal-header {
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+.modal-header .close {
+  margin-top: -2px;
+}
+.modal-title {
+  margin: 0;
+  line-height: 1.42857143;
+}
+.modal-body {
+  position: relative;
+  padding: 15px;
+}
+.modal-footer {
+  padding: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+.modal-scrollbar-measure {
+  position: absolute;
+  top: -9999px;
+  width: 50px;
+  height: 50px;
+  overflow: scroll;
+}
+@media (min-width: 768px) {
+  .modal-dialog {
+    width: 600px;
+    margin: 30px auto;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+  }
+  .modal-sm {
+    width: 300px;
+  }
+}
+@media (min-width: 992px) {
+  .modal-lg {
+    width: 900px;
+  }
+}
+.tooltip {
+  position: absolute;
+  z-index: 1070;
+  display: block;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 12px;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  word-wrap: normal;
+  white-space: normal;
+  filter: alpha(opacity=0);
+  opacity: 0;
+
+  line-break: auto;
+}
+.tooltip.in {
+  filter: alpha(opacity=90);
+  opacity: .9;
+}
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #fff;
+  text-align: center;
+  background-color: #000;
+  border-radius: 4px;
+}
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.top-left .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  margin-bottom: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.top-right .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  margin-bottom: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000;
+}
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-width: 5px 5px 5px 0;
+  border-right-color: #000;
+}
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-width: 5px 0 5px 5px;
+  border-left-color: #000;
+}
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  margin-top: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  margin-top: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000;
+}
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1060;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1.42857143;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  word-wrap: normal;
+  white-space: normal;
+  background-color: #fff;
+  -webkit-background-clip: padding-box;
+          background-clip: padding-box;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, .2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+
+  line-break: auto;
+}
+.popover.top {
+  margin-top: -10px;
+}
+.popover.right {
+  margin-left: 10px;
+}
+.popover.bottom {
+  margin-top: 10px;
+}
+.popover.left {
+  margin-left: -10px;
+}
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+.popover-content {
+  padding: 9px 14px;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.popover > .arrow {
+  border-width: 11px;
+}
+.popover > .arrow:after {
+  content: "";
+  border-width: 10px;
+}
+.popover.top > .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999;
+  border-top-color: rgba(0, 0, 0, .25);
+  border-bottom-width: 0;
+}
+.popover.top > .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  content: " ";
+  border-top-color: #fff;
+  border-bottom-width: 0;
+}
+.popover.right > .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999;
+  border-right-color: rgba(0, 0, 0, .25);
+  border-left-width: 0;
+}
+.popover.right > .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  content: " ";
+  border-right-color: #fff;
+  border-left-width: 0;
+}
+.popover.bottom > .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-width: 0;
+  border-bottom-color: #999;
+  border-bottom-color: rgba(0, 0, 0, .25);
+}
+.popover.bottom > .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  content: " ";
+  border-top-width: 0;
+  border-bottom-color: #fff;
+}
+.popover.left > .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-right-width: 0;
+  border-left-color: #999;
+  border-left-color: rgba(0, 0, 0, .25);
+}
+.popover.left > .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  content: " ";
+  border-right-width: 0;
+  border-left-color: #fff;
+}
+.carousel {
+  position: relative;
+}
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: .6s ease-in-out left;
+       -o-transition: .6s ease-in-out left;
+          transition: .6s ease-in-out left;
+}
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  line-height: 1;
+}
+@media all and (transform-3d), (-webkit-transform-3d) {
+  .carousel-inner > .item {
+    -webkit-transition: -webkit-transform .6s ease-in-out;
+         -o-transition:      -o-transform .6s ease-in-out;
+            transition:         transform .6s ease-in-out;
+
+    -webkit-backface-visibility: hidden;
+            backface-visibility: hidden;
+    -webkit-perspective: 1000px;
+            perspective: 1000px;
+  }
+  .carousel-inner > .item.next,
+  .carousel-inner > .item.active.right {
+    left: 0;
+    -webkit-transform: translate3d(100%, 0, 0);
+            transform: translate3d(100%, 0, 0);
+  }
+  .carousel-inner > .item.prev,
+  .carousel-inner > .item.active.left {
+    left: 0;
+    -webkit-transform: translate3d(-100%, 0, 0);
+            transform: translate3d(-100%, 0, 0);
+  }
+  .carousel-inner > .item.next.left,
+  .carousel-inner > .item.prev.right,
+  .carousel-inner > .item.active {
+    left: 0;
+    -webkit-transform: translate3d(0, 0, 0);
+            transform: translate3d(0, 0, 0);
+  }
+}
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+.carousel-inner > .active {
+  left: 0;
+}
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.carousel-inner > .next {
+  left: 100%;
+}
+.carousel-inner > .prev {
+  left: -100%;
+}
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+.carousel-inner > .active.left {
+  left: -100%;
+}
+.carousel-inner > .active.right {
+  left: 100%;
+}
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #fff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+  background-color: rgba(0, 0, 0, 0);
+  filter: alpha(opacity=50);
+  opacity: .5;
+}
+.carousel-control.left {
+  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));
+  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+  background-repeat: repeat-x;
+}
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));
+  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+  background-repeat: repeat-x;
+}
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #fff;
+  text-decoration: none;
+  filter: alpha(opacity=90);
+  outline: 0;
+  opacity: .9;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+  position: absolute;
+  top: 50%;
+  z-index: 5;
+  display: inline-block;
+  margin-top: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+  left: 50%;
+  margin-left: -10px;
+}
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+  right: 50%;
+  margin-right: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  width: 20px;
+  height: 20px;
+  font-family: serif;
+  line-height: 1;
+}
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  padding-left: 0;
+  margin-left: -30%;
+  text-align: center;
+  list-style: none;
+}
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  background-color: #000 \9;
+  background-color: rgba(0, 0, 0, 0);
+  border: 1px solid #fff;
+  border-radius: 10px;
+}
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #fff;
+}
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #fff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+}
+.carousel-caption .btn {
+  text-shadow: none;
+}
+@media screen and (min-width: 768px) {
+  .carousel-control .glyphicon-chevron-left,
+  .carousel-control .glyphicon-chevron-right,
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -10px;
+    font-size: 30px;
+  }
+  .carousel-control .glyphicon-chevron-left,
+  .carousel-control .icon-prev {
+    margin-left: -10px;
+  }
+  .carousel-control .glyphicon-chevron-right,
+  .carousel-control .icon-next {
+    margin-right: -10px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+.clearfix:before,
+.clearfix:after,
+.dl-horizontal dd:before,
+.dl-horizontal dd:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after,
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after,
+.btn-toolbar:before,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after,
+.nav:before,
+.nav:after,
+.navbar:before,
+.navbar:after,
+.navbar-header:before,
+.navbar-header:after,
+.navbar-collapse:before,
+.navbar-collapse:after,
+.pager:before,
+.pager:after,
+.panel-body:before,
+.panel-body:after,
+.modal-header:before,
+.modal-header:after,
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+.clearfix:after,
+.dl-horizontal dd:after,
+.container:after,
+.container-fluid:after,
+.row:after,
+.form-horizontal .form-group:after,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:after,
+.nav:after,
+.navbar:after,
+.navbar-header:after,
+.navbar-collapse:after,
+.pager:after,
+.panel-body:after,
+.modal-header:after,
+.modal-footer:after {
+  clear: both;
+}
+.center-block {
+  display: block;
+  margin-right: auto;
+  margin-left: auto;
+}
+.pull-right {
+  float: right !important;
+}
+.pull-left {
+  float: left !important;
+}
+.hide {
+  display: none !important;
+}
+.show {
+  display: block !important;
+}
+.invisible {
+  visibility: hidden;
+}
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+.hidden {
+  display: none !important;
+}
+.affix {
+  position: fixed;
+}
+@-ms-viewport {
+  width: device-width;
+}
+.visible-xs,
+.visible-sm,
+.visible-md,
+.visible-lg {
+  display: none !important;
+}
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block {
+  display: none !important;
+}
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  table.visible-xs {
+    display: table !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-block {
+    display: block !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-inline {
+    display: inline !important;
+  }
+}
+@media (max-width: 767px) {
+  .visible-xs-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  table.visible-sm {
+    display: table !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-block {
+    display: block !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  table.visible-md {
+    display: table !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-block {
+    display: block !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  table.visible-lg {
+    display: table !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-block {
+    display: block !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-inline {
+    display: inline !important;
+  }
+}
+@media (min-width: 1200px) {
+  .visible-lg-inline-block {
+    display: inline-block !important;
+  }
+}
+@media (max-width: 767px) {
+  .hidden-xs {
+    display: none !important;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm {
+    display: none !important;
+  }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md {
+    display: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .hidden-lg {
+    display: none !important;
+  }
+}
+.visible-print {
+  display: none !important;
+}
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  table.visible-print {
+    display: table !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+}
+.visible-print-block {
+  display: none !important;
+}
+@media print {
+  .visible-print-block {
+    display: block !important;
+  }
+}
+.visible-print-inline {
+  display: none !important;
+}
+@media print {
+  .visible-print-inline {
+    display: inline !important;
+  }
+}
+.visible-print-inline-block {
+  display: none !important;
+}
+@media print {
+  .visible-print-inline-block {
+    display: inline-block !important;
+  }
+}
+@media print {
+  .hidden-print {
+    display: none !important;
+  }
+}
+/*# sourceMappingURL=bootstrap.css.map */
diff --git a/bbb-web-api/grails-app/assets/stylesheets/errors.css b/bbb-web-api/grails-app/assets/stylesheets/errors.css
new file mode 100755
index 0000000000..1c616d8d94
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/errors.css
@@ -0,0 +1,109 @@
+h1, h2 {
+    margin: 10px 25px 5px;
+}
+
+h2 {
+    font-size: 1.1em;
+}
+
+.filename {
+    font-style: italic;
+}
+
+.exceptionMessage {
+    margin: 10px;
+    border: 1px solid #000;
+    padding: 5px;
+    background-color: #E9E9E9;
+}
+
+.stack,
+.snippet {
+    margin: 0 25px 10px;
+}
+
+.stack,
+.snippet {
+    border: 1px solid #ccc;
+       -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+    -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+            box-shadow: 0 0 2px rgba(0,0,0,0.2);
+}
+
+/* error details */
+.error-details {
+    border-top: 1px solid #FFAAAA;
+       -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+    -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+            box-shadow: 0 0 2px rgba(0,0,0,0.2);
+    border-bottom: 1px solid #FFAAAA;
+       -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+    -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
+            box-shadow: 0 0 2px rgba(0,0,0,0.2);
+    background-color:#FFF3F3;
+    line-height: 1.5;
+    overflow: hidden;
+    padding: 5px;
+    padding-left:25px;
+}
+
+.error-details dt {
+    clear: left;
+    float: left;
+    font-weight: bold;
+    margin-right: 5px;
+}
+
+.error-details dt:after {
+    content: ":";
+}
+
+.error-details dd {
+    display: block;
+}
+
+/* stack trace */
+.stack {
+    padding: 5px;
+    overflow: auto;
+    height: 150px;
+}
+
+/* code snippet */
+.snippet {
+    background-color: #fff;
+    font-family: monospace;
+}
+
+.snippet .line {
+    display: block;
+}
+
+.snippet .lineNumber {
+    background-color: #ddd;
+    color: #999;
+    display: inline-block;
+    margin-right: 5px;
+    padding: 0 3px;
+    text-align: right;
+    width: 3em;
+}
+
+.snippet .error {
+    background-color: #fff3f3;
+    font-weight: bold;
+}
+
+.snippet .error .lineNumber {
+    background-color: #faa;
+    color: #333;
+    font-weight: bold;
+}
+
+.snippet .line:first-child .lineNumber {
+    padding-top: 5px;
+}
+
+.snippet .line:last-child .lineNumber {
+    padding-bottom: 5px;
+}
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/assets/stylesheets/grails.css b/bbb-web-api/grails-app/assets/stylesheets/grails.css
new file mode 100755
index 0000000000..d943f0de18
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/grails.css
@@ -0,0 +1,1059 @@
+html, code, kbd, pre, samp {
+    -ms-text-size-adjust: 100%;
+    -webkit-text-size-adjust: 100%;
+}
+
+html, body {
+    height: 100%;
+    -webkit-overflow-scrolling: touch;
+}
+
+p, ul, pre, h1, h2, h3, h4, h5, h6, h7, h8 {
+    margin: 1em 0;
+}
+
+p {
+    display: block;
+}
+
+h1, h2, h3, h4, h5, h6, h7, h8 {
+    font-weight: bold;
+}
+
+pre {
+    border-radius: 0;
+    border: 0;
+    font-size: 14px;
+}
+
+/* customizing bootstrap nav bar */
+.navbar {
+    margin-bottom: 0px;
+    padding-right: 110px;
+}
+.navbar .container {
+    margin: 10px;
+}
+.navbar-default a {
+    color: #ffffff !important;
+    font-size: 18px !important;
+    text-decoration: none;
+}
+.grails-icon img {
+    width: 40px;
+
+}
+.navbar-default, .navbar-static-top {
+    background-color: #4D8618;
+    border: 0px;
+}
+a.navbar-brand {
+    color: white !important;
+    font-size: 19px !important;
+}
+.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus {
+    background-color: transparent;
+    color: white;
+}
+.navbar-nav>li.active>a {
+    color: white !important;
+}
+.navbar-nav>li>a:hover {
+    background-color: #db4800 !important;
+    color: white !important;
+}
+.navbar-nav>li>a {
+    color: #c0d3db;
+}
+.navbar-default .navbar-toggle .icon-bar {
+    background-color: white;
+}
+.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus {
+    background-color: #db4800;
+}
+
+@media (min-width: 768px) {
+    .container {
+        width: auto;
+    }
+}
+
+/* specific to index.html */
+
+@media (max-width: 999px) {
+    #fork-me {
+        display: none;
+    }
+
+    .navbar {
+        padding-right: 0px;
+    }
+}
+
+#fork-me{
+    position: fixed;
+    padding: 0px 50px 0px 50px;
+    top: 40px;
+    right: -60px;
+    background-color: #a60000;
+    color: #ffffff;
+    font-size: 1em;
+    z-index: 100;
+    transform: rotate(+45deg);
+    text-align: center;
+    font-weight: bolder;
+    border: #c14646;
+    border-style: dashed;
+    border-width: 1px;
+}
+
+#fork-me p {
+    margin: 0em 0;
+}
+
+#band {
+    /*grey =#808080*/
+    background: #79B94C  no-repeat 50% 30%;
+    height: 400px;
+}
+
+.svg #band {
+    background-image: url(../img/grails-cupsonly-logo-white.svg);
+}
+
+.no-svg #band {
+    background-image: url(../img/groovy-logo-white.png);
+}
+
+@media (max-width: 1010px) {
+    #band {
+        background-size: 90%;
+        height: 300px;
+    }
+}
+
+@media (max-width: 690px) {
+    #band {
+        background-size: 80%;
+        height: 200px;
+    }
+}
+
+@media (max-width: 475px) {
+    #band {
+        background-size: 70%;
+        height: 100px;
+    }
+}
+
+#they-use-groovy {
+    width: 100%;
+    height: 450px;
+    background-color: #db4800;
+    margin-bottom: 20px;
+    text-align: center;
+}
+
+#they-use-groovy .item {
+    text-align: center;
+    color: white;
+}
+
+#logos-holder {
+    display: inline-block;
+    padding: 0px;
+    margin: 0px;
+    text-align: center;
+}
+
+#logos-holder .logo {
+    padding: 0px;
+    margin: 0px;
+    display: inline-block;
+    width: 100px;
+    height: 80px;
+    background-size: 95%;
+    background-repeat: no-repeat;
+    background-position: 50% 50%;
+}
+
+@media (min-width: 330px) {
+    #logos-holder {
+        width: 320px;
+    }
+
+    #they-use-groovy {
+        height: 1130px;
+    }
+}
+
+@media (min-width: 475px) {
+    #logos-holder {
+        width: 420px;
+    }
+
+    #they-use-groovy {
+        height: 900px;
+    }
+}
+
+@media (min-width: 690px) {
+    #logos-holder {
+        width: 630px;
+    }
+
+    #they-use-groovy {
+        height: 600px;
+    }
+}
+
+@media (min-width: 1010px) {
+    #logos-holder {
+        width: 940px;
+    }
+
+    #they-use-groovy {
+        height: 450px;
+    }
+}
+
+.centered {
+    text-align: center;
+}
+
+.event-img {
+    margin: -20px -20px 20px -20px;
+    background-repeat: no-repeat;
+    background-position: 50% top;
+    height: 180px;
+}
+
+.event-logo {
+    height: 180px;
+    float: right;
+}
+
+@media (max-width: 1010px) {
+    .event-logo {
+        height: ;
+    }
+
+}
+
+@media (max-width: 690px) {
+    .event-logo {
+        height: 60px;
+    }}
+
+@media (max-width: 475px) {
+    .event-logo {
+        display: none;
+    }
+}
+
+article .content time {
+    font-weight: bold;
+}
+
+.doc-embed {
+    border: 0;
+    width: 100%;
+    min-height: 100%;
+}
+
+.download-table {
+    width: 100%;
+    text-align: center;
+}
+
+.download-table td {
+    width: 20%;
+}
+
+#mc-embedded-subscribe {
+    width: 200px;
+    font-weight: bold;
+}
+
+#mc-embedded-subscribe:hover {
+    background-color: #F2F2F2;
+    font-weight: bold;
+}
+
+#footer .colset-3-footer .col-1 h1, #footer .colset-3-footer .col-2 h1, #footer .colset-3-footer .col-3 h1 {
+    font-size: 15px !important;
+}
+
+.anchor-link:before {
+    content: ' # ';
+    color: lightgray;
+}
+
+.anchor-link:hover:before {
+    color: orange;
+}
+
+code, kbd, pre, samp {
+    font-family: "Source Code Pro", "Consolas", "Monaco", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
+}
+
+#contribute-btn {
+    position: absolute;
+    right: 15px;
+}
+
+@media (max-width: 767px) {
+    #contribute-btn {
+        width: 100%;
+        position: relative;
+        margin-top: 30px;
+        right: 0px;
+    }
+
+    #contribute-btn button {
+        width: 100%;
+        right: 15px;
+    }
+}
+
+@media (min-width: 1200px) {
+    #contribute-btn {
+        top: 25px;
+        right: 15px;
+    }
+}
+
+#big-download-button {
+    float: right;
+    font-size: 30px;
+    padding: 15px;
+    margin: 10px 0px 10px 20px;
+    border: 2px solid #db4800;
+    border-radius: 6px;
+    background-color: #db4800;
+    color: white;
+}
+
+#big-download-button:hover {
+    background-color: #e6e6e6;
+    color: #db4800;
+}
+
+.colset-3-footer .col-1, .colset-3-footer .col-2, .colset-3-footer .col-3 {
+    min-width: 180px;
+    float: left;
+}
+
+.colset-3-footer .col-3 {
+    min-width: 220px;
+}
+
+.colset-3-article article {
+    float: left;
+}
+
+.col1, .col2 {
+    min-width: 300px;
+    float: left;
+}
+
+@media (max-width: 988px) {
+    .col1, .col2 {
+        width: 98% !important;
+        max-width: 98%;
+    }
+
+    .colset-3-article article {
+        width: 98% !important;
+        max-width: 98%;
+    }
+}
+
+body, html {
+    font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+    padding: 0;
+    margin: 0;
+    background: #FFF;
+    color: #343437;
+    line-height: 25px;
+    font-weight: normal;
+    font-size: 14px;
+}
+
+a {
+    color: #db4800;
+    text-decoration: underline;
+}
+
+a:hover {
+    color: #db4800;
+    text-decoration: none
+}
+
+h1 {
+    font-size: 2.125em;
+    margin: .67em 0
+}
+
+h2 {
+    font-size: 1.6875em;
+    font-weight: bold;
+}
+
+h3, #toctitle, .sidebarblock > .content > .title {
+    font-size: 1.375em;
+    font-weight: bold;
+}
+
+h4 {
+    font-size: 1.125em;
+    font-weight: bold;
+}
+
+h5 {
+    font-size: 1.125em;
+    font-weight: bold;
+    color: #db4800;
+}
+
+h6 {
+    font-size: 1.08em;
+    font-weight: normal;
+    color: #db4800;
+}
+
+h7 {
+    font-weight: bold;
+    color: #245f78;
+}
+
+h8 {
+    color: #245f78;
+}
+
+#footer {
+    background: #f2f2f2;
+    text-align: center;
+    font-size: 14px;
+    padding: 20px 0 30px;
+    margin-top: 30px;
+    color: #AAA
+}
+
+#footer .col-right {
+    float: right;
+    width: 300px;
+    text-align: right;
+    padding-top: 10px
+}
+
+#footer .colset-3-footer {
+    color: #222;
+    font-size: 14px
+}
+
+#footer .colset-3-footer:before, #footer .colset-3-footer:after {
+    content: " ";
+    display: table
+}
+
+#footer .colset-3-footer:after {
+    clear: both
+}
+
+#footer .colset-3-footer .col-1, #footer .colset-3-footer .col-2, #footer .colset-3-footer .col-3 {
+    width: 18%;
+    padding: 20px 0 30px;
+    padding-right: 3%;
+    float: left;
+    text-align: left
+}
+
+#footer .colset-3-footer .col-3 {
+    width: 24%;
+}
+
+#footer .colset-3-footer .col-1 h1, #footer .colset-3-footer .col-2 h1, #footer .colset-3-footer .col-3 h1 {
+    font-weight: 600;
+    font-size: 15px;
+    line-height: 30px;
+    margin: 0
+}
+
+#footer .colset-3-footer .col-1 ul, #footer .colset-3-footer .col-2 ul, #footer .colset-3-footer .col-3 ul {
+    list-style-type: none;
+    margin: 0;
+    padding: 0
+}
+
+#footer .colset-3-footer .col-1 ul li, #footer .colset-3-footer .col-2 ul li, #footer .colset-3-footer .col-3 ul li {
+    margin: 0;
+    padding: 0
+}
+
+#footer .colset-3-footer .col-1 ul li a, #footer .colset-3-footer .col-2 ul li a, #footer .colset-3-footer .col-3 ul li a {
+    color: #343437;
+    text-decoration: none
+}
+
+#footer .colset-3-footer .col-1 ul li a:hover, #footer .colset-3-footer .col-2 ul li a:hover, #footer .colset-3-footer .col-3 ul li a:hover {
+    text-decoration: underline
+}
+
+#footer .second a {
+    color: #db4800
+}
+
+.row {
+    position: relative;
+    max-width: 1400px;
+    margin: 0 auto;
+    padding: 0 5%
+}
+
+.row:before, .row:after {
+    content: " ";
+    display: table
+}
+
+.row:after {
+    clear: both
+}
+
+.band {
+    background: #4298b8;
+    height: 400px;
+    margin-bottom: 20px;
+    color: white
+}
+
+.band .item {
+    text-align: center
+}
+
+.band .item:before, .band .item:after {
+    content: " ";
+    display: table
+}
+
+.band .item:after {
+    clear: both
+}
+
+#content {
+    background: white
+}
+
+#content .row:before, #content .row:after {
+    content: " ";
+    display: table
+}
+
+#content .row:after {
+    clear: both
+}
+
+#content .row > h1 {
+    font-size: 34px;
+    line-height: 40px;
+    font-weight: 200;
+    text-align: center;
+    margin: 0;
+    padding: 20px 0
+}
+
+#content hr.row, #content hr.divider {
+    border: 0 none;
+    border-top: 1px solid #EEE;
+    margin: 0 5%;
+    margin-top: 40px
+}
+
+#content hr.divider {
+    margin: 0;
+    margin-top: 40px;
+    margin-bottom: 30px
+}
+
+#content .colset-2-its:before, #content .colset-2-its:after {
+    content: " ";
+    display: table
+}
+
+#content .colset-2-its:after {
+    clear: both
+}
+
+#content .colset-2-its > h1 {
+    padding-bottom: 15px;
+    margin-top: 15px;
+    margin-bottom: 0
+}
+
+#content .colset-2-its > p {
+    margin-top: 0;
+    padding-bottom: 5px;
+    text-align: center;
+    color: #222;
+    font-size: 15px
+}
+
+#content .colset-2-its .col1, #content .colset-2-its .col2 {
+    float: left;
+    width: 48%;
+    padding-right: 1%;
+    padding-left: 1%;
+}
+
+#content .colset-2-its .col2 {
+    padding-left: 1%;
+    padding-right: 1%;
+}
+
+#content .colset-2-its article {
+    padding: 10px 0
+}
+
+#content .colset-2-its article:before, #content .colset-2-its article:after {
+    content: " ";
+    display: table
+}
+
+#content .colset-2-its article:after {
+    clear: both
+}
+
+#content .colset-2-its article .icon {
+    display: block;
+    width: 80px;
+    height: 80px;
+    background-image: url(../img/icons-colset-2-its.png);
+    float: left;
+    margin-top: 12px;
+    margin-right: 15px
+}
+
+#content .colset-2-its article .icon.icon-1 {
+    background-position: 0 0
+}
+
+#content .colset-2-its article .icon.icon-2 {
+    background-position: 0 -80px
+}
+
+#content .colset-2-its article .icon.icon-3 {
+    background-position: 0 -160px
+}
+
+#content .colset-2-its article .icon.icon-4 {
+    background-position: 0 -240px
+}
+
+#content .colset-2-its article .icon.icon-5 {
+    background-position: 0 -320px
+}
+
+#content .colset-2-its article .icon.icon-6 {
+    background-position: 0 -400px
+}
+
+#content .colset-2-its article > h1 {
+    font-size: 19px;
+    font-weight: 600;
+    margin-bottom: 0;
+    line-height: 30px
+}
+
+#content .colset-2-its article p {
+    margin: 0;
+    line-height: 24px;
+    font-size: 14px
+}
+
+#content .first-event-row {
+    padding-top: 30px;
+}
+
+#content .last-event-row {
+    padding-bottom: 30px
+}
+
+#content .colset-3-article > h1 {
+    font-size: 24px
+}
+
+#content .colset-3-article div.content {
+    padding: 20px;
+    padding-bottom: 5px
+}
+
+#content .colset-3-article article {
+    float: left;
+    width: 29%;
+    margin: 10px 2%;
+    -webkit-box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1)
+}
+
+#content .colset-3-article article .img {
+    margin: -20px -20px 20px -20px;
+    background-position: center top;
+    height: 180px
+}
+
+#content .colset-3-article article h1 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: normal;
+    line-height: 25px
+}
+
+#content .colset-3-article article h1 a {
+    color: #343437;
+    cursor: pointer
+}
+
+#content .colset-3-article article h1 a:hover {
+    color: #46a5c8
+}
+
+#content .colset-3-article article p, #content .colset-3-article article time {
+    font-size: 13px
+}
+
+#content .colset-3-article article .author a {
+    color: #db4800
+}
+
+#content .colset-3-article article:first-child {
+    padding-left: 0
+}
+
+#content .colset-3-article article:last-child {
+    padding-right: 0
+}
+
+#content.page-1 .row {
+    padding-top: 10px;
+    padding-bottom: 10px
+}
+
+#content.page-1 .row h1 {
+    text-align: left;
+    font-size: 36px
+}
+
+#content.page-1 .row article {
+    font-size: 14px
+}
+
+#content.page-1 .row article .desc {
+    font-size: 16px
+}
+
+#content.page-1 .row article h1 {
+    margin: 0;
+    paddnig: 0;
+    text-align: left;
+    font-size: 26px
+}
+
+#content.page-1 .row article h2 {
+    margin: 0;
+    paddnig: 0
+}
+
+#content.page-1 .row article h3 {
+    font-weight: bold
+}
+
+#content.page-1 .row article pre {
+    display: block;
+    background: #f2f2f2;
+    padding: 12px 20px
+}
+
+ul.nav-sidebar {
+    margin: 0;
+    margin-top: 20px;
+    padding: 5px 0;
+    border: 1px solid #EEE;
+    list-style-type: none
+}
+
+ul.nav-sidebar li a {
+    display: block;
+    cursor: pointer;
+    padding: 5px 10px;
+    font-weight: 400;
+    text-decoration: none;
+    color: #343437
+}
+
+ul.nav-sidebar li.active a:hover, ul.nav-sidebar li a:hover {
+    color: white;
+    background-color: #db4800;
+}
+
+ul.nav-sidebar li.active a {
+    background-color: #f2f2f2
+}
+
+.table {
+    margin: 20px 0
+}
+
+.table thead tr th {
+    padding: 10px;
+    font-weight: normal;
+    font-size: 18px
+}
+
+.table tbody tr td {
+    vertical-align: top;
+    font-size: 12px;
+    padding: 10px;
+    border-top: 1px solid #EEE
+}
+
+*, *:after, *::before {
+    -moz-box-sizing: border-box;
+    box-sizing: border-box
+}
+
+body {
+    background: #444
+}
+
+html.noScroll {
+    overflow: hidden
+}
+
+html.noScroll body, html.noScroll .st-container, html.noScroll .st-pusher, html.noScroll .st-content {
+    overflow: hidden
+}
+
+html, body, .st-container, .st-pusher, .st-content {
+    overflow: auto
+}
+
+.sign-in-fa-icon:before {
+    font-family: FontAwesome;
+    content: '\f090';
+    padding-right: 10px;
+}
+
+#st-container {
+    height: 100%;
+}
+
+.st-content {
+    background: white
+}
+
+.st-content, .st-content-inner {
+    position: relative;
+    height: 100%;
+}
+
+.st-container {
+    position: relative;
+    overflow: hidden
+}
+
+.st-pusher {
+    position: relative;
+    left: 0;
+    z-index: 99;
+    height: 100%;
+    -webkit-transition: -webkit-transform .5s;
+    transition: transform .5s
+}
+
+.st-pusher::after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 0;
+    height: 0;
+    background: rgba(0, 0, 0, 0.3);
+    content: '';
+    opacity: 0;
+    -webkit-transition: opacity .5s, width .1s .5s, height .1s .5s;
+    transition: opacity .5s, width .1s .5s, height .1s .5s
+}
+
+.st-menu-open .st-pusher::after {
+    width: 100%;
+    height: 100%;
+    opacity: 1;
+    -webkit-transition: opacity .5s;
+    transition: opacity .5s
+}
+
+.st-menu {
+    position: fixed;
+    top: 0;
+    left: auto;
+    z-index: 100;
+    visibility: hidden;
+    width: 300px;
+    height: 100%;
+    background: #79B94C;
+    -webkit-transition: all .5s;
+    transition: all .5s;
+    right: -600px
+}
+
+.st-menu::after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.2);
+    content: '';
+    opacity: 1;
+    -webkit-transition: opacity .5s;
+    transition: opacity .5s
+}
+
+.st-menu-open .st-menu::after {
+    width: 0;
+    height: 0;
+    opacity: 0;
+    -webkit-transition: opacity .5s, width .1s .5s, height .1s .5s;
+    transition: opacity .5s, width .1s .5s, height .1s .5s
+}
+
+.st-menu ul {
+    margin: 0;
+    padding: 0;
+    list-style: none
+}
+
+.st-menu h2 {
+    margin: 0;
+    padding: 1em;
+    color: white;
+    text-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
+    font-weight: 300;
+    font-size: 2em
+}
+
+.st-menu ul li {
+    display: block
+}
+
+.st-menu ul li a {
+    display: block;
+    position: relative;
+    padding: 1em 1em 1em 45px;
+    outline: 0;
+    box-shadow: inset 0 -1px rgba(0, 0, 0, 0.2);
+    color: #f3efe0;
+    text-shadow: 0 0 1px rgba(255, 255, 255, 0.1);
+    letter-spacing: 1px;
+    font-weight: 400;
+    text-decoration: none
+}
+
+.st-menu ul li a span.fa {
+    display: block;
+    position: absolute;
+    left: 12px;
+    top: 17px;
+    font-size: 20px;
+    width: 30px;
+    text-align: center
+}
+
+.st-menu ul li a span.fa.fa-tasks, .st-menu ul li a span.fa.fa-envelope {
+    top: 18px;
+    font-size: 18px
+}
+
+.st-menu ul li:first-child a {
+    box-shadow: inset 0 -1px rgba(0, 0, 0, 0.2), inset 0 1px rgba(0, 0, 0, 0.2)
+}
+
+.st-menu ul li a:hover {
+    background: rgba(0, 0, 0, 0.2);
+    box-shadow: inset 0 -1px rgba(0, 0, 0, 0);
+    color: #fff
+}
+
+.st-effect-9.st-container {
+    -webkit-perspective: 10000px;
+    perspective: 10000px
+}
+
+.st-effect-9 .st-pusher {
+    -webkit-transform-style: preserve-3d;
+    transform-style: preserve-3d
+}
+
+.st-effect-9.st-menu-open .st-pusher {
+    -webkit-transform: translate3d(0, 0, -300px);
+    transform: translate3d(0, 0, -300px)
+}
+
+.st-effect-9.st-menu {
+    right: -600px;
+    opacity: 1;
+    -webkit-transform: translate3d(-100%, 0, 0);
+    transform: translate3d(-100%, 0, 0)
+}
+
+.st-effect-9.st-menu-open .st-effect-9.st-menu {
+    visibility: visible;
+    right: -300px
+}
+
+.st-effect-9.st-menu::after {
+    display: none
+}
+
+/* Video from the learn page */
+.presentations {
+    margin-top: 30px;
+    margin-bottom: 30px;
+}
+
+.presentations img.screenshot {
+    float: left;
+    margin-right: 40px;
+    margin-top: 1em;
+    margin-bottom: 0px;
+    width: 300px;
+    height: auto;
+}
+
+.presentations .metadata {
+    display: table-cell;
+    min-width: 328px;
+}
+
+.presentations .title {
+    margin-top: 1em !important;
+    margin-bottom: 0.5em !important;
+}
+
+
+.presentations .speaker {
+    color: #245f78;
+    margin-bottom: 0.5em;
+}
+
+.presentations .summary {
+    line-height: 1.3;
+}
+
+.presentations .urls {
+}
+
+@media screen and (max-width: 767px) {
+    .presentations .img.screenshot, .video .metadata {
+        float: none;
+    }
+}
diff --git a/bbb-web-api/grails-app/assets/stylesheets/main.css b/bbb-web-api/grails-app/assets/stylesheets/main.css
new file mode 100755
index 0000000000..c1f43fb17d
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/main.css
@@ -0,0 +1,574 @@
+/* FONT STACK */
+body,
+input, select, textarea {
+    font-family: "Open Sans", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    line-height: 1.1;
+}
+
+/* BASE LAYOUT */
+
+html {
+    background-color: #ddd;
+    background-image: -moz-linear-gradient(center top, #aaa, #ddd);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #aaa), color-stop(1, #ddd));
+    background-image: linear-gradient(top, #aaa, #ddd);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#aaaaaa', EndColorStr = '#dddddd');
+    background-repeat: no-repeat;
+    height: 100%;
+    /* change the box model to exclude the padding from the calculation of 100% height (IE8+) */
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+html.no-cssgradients {
+    background-color: #aaa;
+}
+
+html * {
+    margin: 0;
+}
+
+body {
+    background: #ffffff;
+    color: #333333;
+    overflow-x: hidden; /* prevents box-shadow causing a horizontal scrollbar in firefox when viewport < 960px wide */
+    -moz-box-shadow: 0 0 0.3em #4D8618;
+    -webkit-box-shadow: 0 0 0.3em #4D8618;
+    box-shadow: 0 0 0.3em #4D8618;
+}
+
+#grailsLogo {
+    background-color: #abbf78;
+}
+
+a:hover, a:active {
+    outline: none; /* prevents outline in webkit on active links but retains it for tab focus */
+}
+
+h1, h2, h3 {
+    font-weight: normal;
+    font-size: 1.25em;
+    margin: 0.8em 0 0.3em 0;
+}
+
+ul {
+    padding: 0;
+}
+
+img {
+    border: 0;
+}
+
+/* GENERAL */
+
+#grailsLogo a {
+    display: inline-block;
+    margin: 1em;
+}
+
+.content {
+}
+
+.content h1 {
+    border-bottom: 1px solid #CCCCCC;
+    margin: 0.8em 1em 0.3em;
+    padding: 0 0.25em;
+}
+
+.scaffold-list h1 {
+    border: none;
+}
+
+.footer {
+    background: #48802c;
+    color: #000;
+    clear: both;
+    font-size: 0.8em;
+    margin-top: 1.5em;
+    padding: 1em;
+    min-height: 1em;
+}
+
+.footer a {
+    color: #4D8618;
+}
+
+.spinner {
+    background: url(../images/spinner.gif) 50% 50% no-repeat transparent;
+    height: 16px;
+    width: 16px;
+    padding: 0.5em;
+    position: absolute;
+    right: 0;
+    top: 0;
+    text-indent: -9999px;
+}
+
+/* NAVIGATION MENU */
+
+.nav {
+    zoom: 1;
+}
+
+.nav ul {
+    overflow: hidden;
+    padding-left: 0;
+    zoom: 1;
+}
+
+.nav li {
+    display: block;
+    float: left;
+    list-style-type: none;
+    margin-right: 0.5em;
+    padding: 0;
+}
+
+.nav a {
+    color: #666666;
+    display: block;
+    padding: 0.25em 0.7em;
+    text-decoration: none;
+    -moz-border-radius: 0.3em;
+    -webkit-border-radius: 0.3em;
+    border-radius: 0.3em;
+}
+
+.nav a:active, .nav a:visited {
+    color: #666666;
+}
+
+.nav a:focus, .nav a:hover {
+    background-color: #999999;
+    color: #ffffff;
+    outline: none;
+    text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
+}
+
+.no-borderradius .nav a:focus, .no-borderradius .nav a:hover {
+    background-color: transparent;
+    color: #444444;
+    text-decoration: underline;
+}
+
+.nav a.home, .nav a.list, .nav a.create {
+    background-position: 0.7em center;
+    background-repeat: no-repeat;
+    text-indent: 25px;
+}
+
+.nav a.home {
+    background-image: url(../images/skin/house.png);
+}
+
+.nav a.list {
+    background-image: url(../images/skin/database_table.png);
+}
+
+.nav a.create {
+    background-image: url(../images/skin/database_add.png);
+}
+
+.nav li.dropdown.open ul.dropdown-menu {
+    background-color: #4D8618;
+}
+
+/* CREATE/EDIT FORMS AND SHOW PAGES */
+
+fieldset,
+.property-list {
+    margin: 0.6em 1.25em 0 1.25em;
+    padding: 0.3em 1.8em 1.25em;
+    position: relative;
+    zoom: 1;
+    border: none;
+}
+
+.property-list .fieldcontain {
+    list-style: none;
+    overflow: hidden;
+    zoom: 1;
+}
+
+.fieldcontain {
+    margin-top: 1em;
+}
+
+.fieldcontain label,
+.fieldcontain .property-label {
+    color: #666666;
+    text-align: right;
+    width: 25%;
+}
+
+.fieldcontain .property-label {
+    float: left;
+}
+
+.fieldcontain .property-value {
+    display: block;
+    margin-left: 27%;
+}
+
+label {
+    cursor: pointer;
+    display: inline-block;
+    margin: 0 0.25em 0 0;
+}
+
+input, select, textarea {
+    background-color: #fcfcfc;
+    border: 1px solid #cccccc;
+    font-size: 1em;
+    padding: 0.2em 0.4em;
+}
+
+select {
+    padding: 0.2em 0.2em 0.2em 0;
+}
+
+select[multiple] {
+    vertical-align: top;
+}
+
+textarea {
+    width: 250px;
+    height: 150px;
+    overflow: auto; /* IE always renders vertical scrollbar without this */
+    vertical-align: top;
+}
+
+input[type=checkbox], input[type=radio] {
+    background-color: transparent;
+    border: 0;
+    padding: 0;
+}
+
+input:focus, select:focus, textarea:focus {
+    background-color: #ffffff;
+    border: 1px solid #eeeeee;
+    outline: 0;
+    -moz-box-shadow: 0 0 0.5em #ffffff;
+    -webkit-box-shadow: 0 0 0.5em #ffffff;
+    box-shadow: 0 0 0.5em #ffffff;
+}
+
+.required-indicator {
+    color: #cc0000;
+    display: inline-block;
+    font-weight: bold;
+    margin-left: 0.3em;
+    position: relative;
+    top: 0.1em;
+}
+
+ul.one-to-many {
+    display: inline-block;
+    list-style-position: inside;
+    vertical-align: top;
+}
+
+ul.one-to-many li.add {
+    list-style-type: none;
+}
+
+/* EMBEDDED PROPERTIES */
+
+fieldset.embedded {
+    background-color: transparent;
+    border: 1px solid #CCCCCC;
+    margin-left: 0;
+    margin-right: 0;
+    padding-left: 0;
+    padding-right: 0;
+    -moz-box-shadow: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+}
+
+fieldset.embedded legend {
+    margin: 0 1em;
+}
+
+/* MESSAGES AND ERRORS */
+
+.errors,
+.message {
+    font-size: 0.8em;
+    line-height: 2;
+    margin: 1em 2em;
+    padding: 0.25em;
+}
+
+.message {
+    background: #f3f3ff;
+    border: 1px solid #b2d1ff;
+    color: #006dba;
+    -moz-box-shadow: 0 0 0.25em #b2d1ff;
+    -webkit-box-shadow: 0 0 0.25em #b2d1ff;
+    box-shadow: 0 0 0.25em #b2d1ff;
+}
+
+.errors {
+    background: #fff3f3;
+    border: 1px solid #ffaaaa;
+    color: #cc0000;
+    -moz-box-shadow: 0 0 0.25em #ff8888;
+    -webkit-box-shadow: 0 0 0.25em #ff8888;
+    box-shadow: 0 0 0.25em #ff8888;
+}
+
+.errors ul,
+.message {
+    padding: 0;
+}
+
+.errors li {
+    list-style: none;
+    background: transparent url(../images/skin/exclamation.png) 0.5em 50% no-repeat;
+    text-indent: 2.2em;
+}
+
+.message {
+    background: transparent url(../images/skin/information.png) 0.5em 50% no-repeat;
+    text-indent: 2.2em;
+}
+
+/* form fields with errors */
+
+.error input, .error select, .error textarea {
+    background: #fff3f3;
+    border-color: #ffaaaa;
+    color: #cc0000;
+}
+
+.error input:focus, .error select:focus, .error textarea:focus {
+    -moz-box-shadow: 0 0 0.5em #ffaaaa;
+    -webkit-box-shadow: 0 0 0.5em #ffaaaa;
+    box-shadow: 0 0 0.5em #ffaaaa;
+}
+
+/* same effects for browsers that support HTML5 client-side validation (these have to be specified separately or IE will ignore the entire rule) */
+
+input:invalid, select:invalid, textarea:invalid {
+    background: #fff3f3;
+    border-color: #ffaaaa;
+    color: #cc0000;
+}
+
+input:invalid:focus, select:invalid:focus, textarea:invalid:focus {
+    -moz-box-shadow: 0 0 0.5em #ffaaaa;
+    -webkit-box-shadow: 0 0 0.5em #ffaaaa;
+    box-shadow: 0 0 0.5em #ffaaaa;
+}
+
+/* TABLES */
+
+table {
+    border-top: 1px solid #DFDFDF;
+    border-collapse: collapse;
+    width: 100%;
+    margin-bottom: 1em;
+}
+
+tr {
+    border: 0;
+}
+
+tr>td:first-child, tr>th:first-child {
+    padding-left: 1.25em;
+}
+
+tr>td:last-child, tr>th:last-child {
+    padding-right: 1.25em;
+}
+
+td, th {
+    line-height: 1.5em;
+    padding: 0.5em 0.6em;
+    text-align: left;
+    vertical-align: top;
+}
+
+th {
+    background-color: #efefef;
+    background-image: -moz-linear-gradient(top, #ffffff, #eaeaea);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffffff), color-stop(1, #eaeaea));
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#ffffff', EndColorStr = '#eaeaea');
+    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eaeaea')";
+    color: #666666;
+    font-weight: bold;
+    line-height: 1.7em;
+    padding: 0.2em 0.6em;
+}
+
+thead th {
+    white-space: nowrap;
+}
+
+th a {
+    display: block;
+    text-decoration: none;
+}
+
+th a:link, th a:visited {
+    color: #666666;
+}
+
+th a:hover, th a:focus {
+    color: #333333;
+}
+
+th.sortable a {
+    background-position: right;
+    background-repeat: no-repeat;
+    padding-right: 1.1em;
+}
+
+th.asc a {
+    background-image: url(../images/skin/sorted_asc.gif);
+}
+
+th.desc a {
+    background-image: url(../images/skin/sorted_desc.gif);
+}
+
+.odd {
+    background: #f7f7f7;
+}
+
+.even {
+    background: #ffffff;
+}
+
+th:hover, tr:hover {
+    background: #79b94c;
+}
+
+/* PAGINATION */
+
+.pagination {
+    border-top: 0;
+    margin: 0.8em 1em 0.3em;
+    padding: 0.3em 0.2em;
+    text-align: center;
+    -moz-box-shadow: 0 0 3px 1px #AAAAAA;
+    -webkit-box-shadow: 0 0 3px 1px #AAAAAA;
+    box-shadow: 0 0 3px 1px #AAAAAA;
+    background-color: #EFEFEF;
+}
+
+.pagination a,
+.pagination .currentStep {
+    color: #666666;
+    display: inline-block;
+    margin: 0 0.1em;
+    padding: 0.25em 0.7em;
+    text-decoration: none;
+    -moz-border-radius: 0.3em;
+    -webkit-border-radius: 0.3em;
+    border-radius: 0.3em;
+}
+
+.pagination a:hover, .pagination a:focus,
+.pagination .currentStep {
+    background-color: #999999;
+    color: #ffffff;
+    outline: none;
+    text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
+}
+
+.no-borderradius .pagination a:hover, .no-borderradius .pagination a:focus,
+.no-borderradius .pagination .currentStep {
+    background-color: transparent;
+    color: #444444;
+    text-decoration: underline;
+}
+
+/* ACTION BUTTONS */
+
+.buttons {
+    background-color: #efefef;
+    overflow: hidden;
+    padding: 0.3em;
+    -moz-box-shadow: 0 0 3px 1px #aaaaaa;
+    -webkit-box-shadow: 0 0 3px 1px #aaaaaa;
+    box-shadow: 0 0 3px 1px #aaaaaa;
+    margin: 0.1em 0 0 0;
+    border: none;
+}
+
+.buttons input,
+.buttons a {
+    background-color: transparent;
+    border: 0;
+    color: #666666;
+    cursor: pointer;
+    display: inline-block;
+    margin: 0 0.25em 0;
+    overflow: visible;
+    padding: 0.25em 0.7em;
+    text-decoration: none;
+
+    -moz-border-radius: 0.3em;
+    -webkit-border-radius: 0.3em;
+    border-radius: 0.3em;
+}
+
+.buttons input:hover, .buttons input:focus,
+.buttons a:hover, .buttons a:focus {
+    background-color: #999999;
+    color: #ffffff;
+    outline: none;
+    text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
+    -moz-box-shadow: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+}
+
+.no-borderradius .buttons input:hover, .no-borderradius .buttons input:focus,
+.no-borderradius .buttons a:hover, .no-borderradius .buttons a:focus {
+    background-color: transparent;
+    color: #444444;
+    text-decoration: underline;
+}
+
+.buttons .delete, .buttons .edit, .buttons .save {
+    background-position: 0.7em center;
+    background-repeat: no-repeat;
+    text-indent: 25px;
+}
+
+.buttons .delete {
+    background-image: url(../images/skin/database_delete.png);
+}
+
+.buttons .edit {
+    background-image: url(../images/skin/database_edit.png);
+}
+
+.buttons .save {
+    background-image: url(../images/skin/database_save.png);
+}
+
+a.skip {
+    position: absolute;
+    left: -9999px;
+}
+
+.grails-logo-container {
+    background:#79b94c no-repeat 50% 30%;
+    margin-bottom: 20px;
+    color: white;
+    height:300px;
+    text-align:center;"
+}
+
+img.grails-logo {
+    height:340px;
+    margin-top:-10px;
+}
diff --git a/bbb-web-api/grails-app/assets/stylesheets/mobile.css b/bbb-web-api/grails-app/assets/stylesheets/mobile.css
new file mode 100755
index 0000000000..552abd91b5
--- /dev/null
+++ b/bbb-web-api/grails-app/assets/stylesheets/mobile.css
@@ -0,0 +1,82 @@
+/* Styles for mobile devices */
+
+@media screen and (max-width: 480px) {
+    .nav {
+        padding: 0.5em;
+    }
+
+    .nav li {
+        margin: 0 0.5em 0 0;
+        padding: 0.25em;
+    }
+
+    /* Hide individual steps in pagination, just have next & previous */
+    .pagination .step, .pagination .currentStep {
+        display: none;
+    }
+
+    .pagination .prevLink {
+        float: left;
+    }
+
+    .pagination .nextLink {
+        float: right;
+    }
+
+    /* pagination needs to wrap around floated buttons */
+    .pagination {
+        overflow: hidden;
+    }
+
+    /* slightly smaller margin around content body */
+    fieldset,
+    .property-list {
+        padding: 0.3em 1em 1em;
+    }
+
+    input, textarea {
+        width: 100%;
+           -moz-box-sizing: border-box;
+        -webkit-box-sizing: border-box;
+            -ms-box-sizing: border-box;
+                box-sizing: border-box;
+    }
+
+    select, input[type=checkbox], input[type=radio], input[type=submit], input[type=button], input[type=reset] {
+        width: auto;
+    }
+
+    /* hide all but the first column of list tables */
+    .scaffold-list td:not(:first-child),
+    .scaffold-list th:not(:first-child) {
+        display: none;
+    }
+
+    .scaffold-list thead th {
+        text-align: center;
+    }
+
+    /* stack form elements */
+    .fieldcontain {
+        margin-top: 0.6em;
+    }
+
+    .fieldcontain label,
+    .fieldcontain .property-label,
+    .fieldcontain .property-value {
+        display: block;
+        float: none;
+        margin: 0 0 0.25em 0;
+        text-align: left;
+        width: auto;
+    }
+
+    .errors ul,
+    .message p {
+        margin: 0.5em;
+    }
+
+    .error ul {
+        margin-left: 0;
+    }
+}
diff --git a/bbb-web-api/grails-app/conf/application.groovy b/bbb-web-api/grails-app/conf/application.groovy
new file mode 100755
index 0000000000..edaed0ec8c
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/application.groovy
@@ -0,0 +1,7 @@
+
+// Needed for backwards compatibility of JSON Builder.
+//grails.json.legacy.builder = true
+
+
+
+
diff --git a/bbb-web-api/grails-app/conf/application.properties b/bbb-web-api/grails-app/conf/application.properties
new file mode 100755
index 0000000000..10e61bc7b2
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/application.properties
@@ -0,0 +1,220 @@
+#
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+#
+# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+#
+# This program 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.0 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, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# These are the default properites for BigBlueButton Web application
+
+# Default loglevel. 
+appLogLevel=DEBUG
+
+#----------------------------------------------------
+# Directory where BigBlueButton stores uploaded slides
+presentationDir=/var/bigbluebutton
+
+#----------------------------------------------------
+# Directory where SWFTOOLS (pdf2swf, jpeg2swf, png2swf) are located
+swfToolsDir=/usr/bin
+
+#----------------------------------------------------
+# Directory where ImageMagick's convert executable is located
+imageMagickDir=/usr/bin
+
+#----------------------------------------------------
+# Use fullpath to ghostscript executable since the exec names are different
+# for each platform.
+ghostScriptExec=/usr/bin/gs
+
+#----------------------------------------------------
+# Fonts directory passed into PDF2SWF to support highlighting of texts
+# in the SWF slides.
+fontsDir=/usr/share/fonts
+
+#----------------------------------------------------
+# This is a workaround for a problem converting PDF files, referenced at 
+# http://groups.google.com/group/comp.lang.postscript/browse_thread/thread/c2e264ca76534ce0?pli=1
+noPdfMarkWorkaround=/etc/bigbluebutton/nopdfmark.ps
+
+#----------------------------------------------------
+# These will be copied in cases where the conversion process
+# fails to generate a slide from the uploaded presentation
+BLANK_SLIDE=/var/bigbluebutton/blank/blank-slide.swf
+BLANK_THUMBNAIL=/var/bigbluebutton/blank/blank-thumb.png
+
+#----------------------------------------------------
+# Number of minutes the conversion should take. If it takes
+# more than this time, cancel the conversion process.
+maxConversionTime=5
+
+#----------------------------------------------------
+# Maximum number of pages allowed for an uploaded presentation (default 100).
+maxNumPages=200
+
+#----------------------------------------------------
+# Maximum swf file size for load to the client (default 500000).
+MAX_SWF_FILE_SIZE=500000
+
+#----------------------------------------------------
+# Maximum allowed number of place object tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
+placementsThreshold=8000
+
+# Maximum allowed number of bitmap images in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
+imageTagThreshold=8000
+
+# Maximum allowed number of define text tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 2500)
+defineTextThreshold=2500
+
+#------------------------------------
+# Number of threads in the pool to do the presentation conversion.
+#------------------------------------
+numConversionThreads=2
+
+#----------------------------------------------------
+# Additional conversion of the presentation slides to SVG
+# to be used in the HTML5 client
+svgImagesRequired=false
+
+# Default number of digits for voice conference users joining through the PSTN.
+defaultNumDigitsForTelVoice=5
+
+#----------------------------------------------------
+# Default dial access number
+defaultDialAccessNumber=613-555-1234
+
+#----------------------------------------------------
+# Default welcome message to display when the participant joins the web
+# conference. This is only used for the old scheduling which will be
+# removed in the future. Use the API to create a conference.
+defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner).  Use a headset to avoid causing background noise for others.<br>
+defaultWelcomeMessageFooter=This server is running <a href="http://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>.
+
+# Default maximum number of users a meeting can have.
+# Doesn't get enforced yet but is the default value when the create
+# API doesn't pass a value.
+defaultMaxUsers=20
+
+# Default duration of the meeting in minutes.
+# Current default is 0 (meeting doesn't end).
+defaultMeetingDuration=0
+
+# Remove the meeting from memory when the end API is called.
+# This allows 3rd-party apps to recycle the meeting right-away
+# instead of waiting for the meeting to expire (see below).
+removeMeetingWhenEnded=true
+
+# The number of minutes before the system removes the meeting from memory.
+defaultMeetingExpireDuration=1
+
+# The number of minutes the system waits when a meeting is created and when
+# a user joins. If after this period, a user hasn't joined, the meeting is
+# removed from memory.
+defaultMeetingCreateJoinDuration=5
+
+# Disable recording by default. 
+#   true - don't record even if record param in the api call is set to record
+#   false - when record param is passed from api, override this default
+disableRecordingDefault=false
+
+# Start recording when first user joins the meeting.
+# For backward compatibility with 0.81 where whole meeting
+# is recorded.  
+autoStartRecording=false
+
+# Allow the user to start/stop recording.
+allowStartStopRecording=true
+
+#----------------------------------------------------
+# This URL is where the BBB client is accessible. When a user sucessfully
+# enters a name and password, she is redirected here to load the client.
+bigbluebutton.web.serverURL=http://192.168.23.50
+
+
+#----------------------------------------------------
+# Assign URL where the logged-out participant will be redirected after sign-out.
+# If "default", it returns to bigbluebutton.web.serverURL
+bigbluebutton.web.logoutURL=default
+
+# The url of the BigBlueButton client. User's will be redirected here when
+# successfully joining the meeting.
+defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
+#defaultClientUrl=http://192.168.0.235/3rd-party.html
+
+# The default avatar image to display if nothing is passed on the JOIN API (avatarURL)
+# call. This avatar is displayed if the user isn't sharing the webcam and
+# the option (displayAvatar) is enabled in config.xml
+defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png
+
+# The URL of the default configuration
+defaultConfigURL=${bigbluebutton.web.serverURL}/client/conf/config.xml
+
+apiVersion=1.0
+
+# Salt which is used by 3rd-party apps to authenticate api calls
+securitySalt=676c7ca0eefbb9f5c9cd640a14cd6521
+
+# Directory where we drop the <meeting-id-recorded>.done file
+recordStatusDir=/var/bigbluebutton/recording/status/recorded
+
+redisHost=127.0.0.1
+redisPort=6379
+
+# The directory where the published/unpublised recordings are located. This is for
+# the get recording* api calls
+publishedDir=/var/bigbluebutton/published
+unpublishedDir=/var/bigbluebutton/unpublished
+
+# The directory where the pre-built configs are stored
+configDir=/var/bigbluebutton/configs
+
+# If the API is enabled.
+serviceEnabled = true
+
+# Test voiceBridge number
+testVoiceBridge=99999
+testConferenceMock=conference-mock-default
+
+#------------------------------------------------------
+# These properties are used to test the conversion process.
+# Conference name folder in ${presentationDir} (see above)
+beans.presentationService.testConferenceMock=${testConferenceMock}
+
+# Conference room folder in ${presentationDir}/${testConferenceMock}
+beans.presentationService.testRoomMock=conference-mock-default
+# Uploaded presentation name
+beans.presentationService.testPresentationName=appkonference
+# Uploaded presentation file
+beans.presentationService.testUploadedPresentation=appkonference.txt
+# Default Uploaded presentation file
+beans.presentationService.defaultUploadedPresentation=${bigbluebutton.web.serverURL}/default.pdf
+
+#----------------------------------------------------
+# The URL where the presentations will be loaded from.
+#----------------------------------------------------
+beans.presentationService.presentationBaseUrl=${bigbluebutton.web.serverURL}/bigbluebutton/presentation
+
+#----------------------------------------------------
+# Inject values into grails service beans
+beans.presentationService.presentationDir=${presentationDir}
+
+#----------------------------------------------------
+# Specify which IPs can do cross domain requests
+accessControlAllowOrigin=${bigbluebutton.web.serverURL}
+
+#----------------------------------------------------
+# The lapsus of seconds for polling the BBB Server in order to check if it's down.
+# After 5 tries if there isn't response, it will be declared down
+checkBBBServerEvery=10
diff --git a/bbb-web-api/grails-app/conf/application.yml b/bbb-web-api/grails-app/conf/application.yml
new file mode 100755
index 0000000000..5b711d530a
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/application.yml
@@ -0,0 +1,129 @@
+---
+hibernate:
+    cache:
+        queries: false
+        use_second_level_cache: true
+        use_query_cache: false
+        region.factory_class: 'org.hibernate.cache.ehcache.EhCacheRegionFactory'
+
+dataSource:
+    pooled: true
+    jmxExport: true
+    driverClassName: org.h2.Driver
+    username: sa
+    password:
+
+environments:
+    development:
+        dataSource:
+            dbCreate: create-drop
+            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
+    test:
+        dataSource:
+            dbCreate: update
+            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
+    production:
+        dataSource:
+            dbCreate: update
+            url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
+            properties:
+                jmxEnabled: true
+                initialSize: 5
+                maxActive: 50
+                minIdle: 5
+                maxIdle: 25
+                maxWait: 10000
+                maxAge: 600000
+                timeBetweenEvictionRunsMillis: 5000
+                minEvictableIdleTimeMillis: 60000
+                validationQuery: SELECT 1
+                validationQueryTimeout: 3
+                validationInterval: 15000
+                testOnBorrow: true
+                testWhileIdle: true
+                testOnReturn: false
+                jdbcInterceptors: ConnectionState
+                defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
+
+---
+---
+grails:
+    profile: web
+    codegen:
+        defaultPackage: org.bigbluebutton.web
+    spring:
+        transactionManagement:
+            proxies: false
+info:
+    app:
+        name: '@info.app.name@'
+        version: '@info.app.version@'
+        grailsVersion: '@info.app.grailsVersion@'
+spring:
+
+    groovy:
+        template:
+            check-template-location: false
+
+---
+grails:
+    json:
+        legacy:
+            builder: true
+    mime:
+        disable:
+            accept:
+                header:
+                    userAgents:
+                        - Gecko
+                        - WebKit
+                        - Presto
+                        - Trident
+        types:
+            all: '*/*'
+            atom: application/atom+xml
+            css: text/css
+            csv: text/csv
+            form: application/x-www-form-urlencoded
+            html:
+              - text/html
+              - application/xhtml+xml
+            js: text/javascript
+            json:
+              - application/json
+              - text/json
+            multipartForm: multipart/form-data
+            pdf: application/pdf
+            rss: application/rss+xml
+            text: text/plain
+            hal:
+              - application/hal+json
+              - application/hal+xml
+            xml:
+              - text/xml
+              - application/xml
+    urlmapping:
+        cache:
+            maxsize: 1000
+    controllers:
+        defaultScope: singleton
+    converters:
+        encoding: UTF-8
+    views:
+        default:
+            codec: html
+        gsp:
+            encoding: UTF-8
+            htmlcodec: xml
+            codecs:
+                expression: html
+                scriptlets: html
+                taglib: none
+                staticparts: none
+endpoints:
+    jmx:
+        unique-names: true
+
+server:
+   contextPath: /bigbluebutton
+   port: 8888
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/conf/logback.groovy b/bbb-web-api/grails-app/conf/logback.groovy
new file mode 100755
index 0000000000..42017b0487
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/logback.groovy
@@ -0,0 +1,34 @@
+import grails.util.BuildSettings
+import grails.util.Environment
+//import org.apache.log4j.DailyRollingFileAppender
+
+scan("30 seconds")
+
+// See http://logback.qos.ch/manual/groovy.html for details on configuration
+appender('STDOUT', ConsoleAppender) {
+    encoder(PatternLayoutEncoder) {
+        pattern = "%level %logger - %msg%n"
+    }
+}
+
+//appender('DAILY_ROLLING_FILE', DailyRollingFileAppender) {
+//    encoder(PatternLayoutEncoder) {
+//        pattern = "%level %logger - %msg%n"
+//    }
+//}
+
+logger("org.bigbluebutton", DEBUG, ["STDOUT"])
+
+root(ERROR, ['STDOUT'])
+
+def targetDir = BuildSettings.TARGET_DIR
+if (Environment.isDevelopmentMode() && targetDir) {
+    appender("FULL_STACKTRACE", FileAppender) {
+        file = "${targetDir}/stacktrace.log"
+        append = true
+        encoder(PatternLayoutEncoder) {
+            pattern = "%level %logger - %msg%n"
+        }
+    }
+    logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
+}
diff --git a/bbb-web-api/grails-app/conf/spring/bbb-redis-messaging.xml b/bbb-web-api/grails-app/conf/spring/bbb-redis-messaging.xml
new file mode 100755
index 0000000000..279d8c6e55
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/bbb-redis-messaging.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program 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.0 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, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:util="http://www.springframework.org/schema/util"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+			http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+			http://www.springframework.org/schema/util 
+			http://www.springframework.org/schema/util/spring-util-2.0.xsd
+			">
+
+    <bean id="redisStorageService" class="org.bigbluebutton.api.messaging.RedisStorageService"
+                    init-method="start" destroy-method="stop">
+      <property name="host" value="${redisHost}" />
+      <property name="port" value="${redisPort}" />
+    </bean>
+    	
+    <bean id="messageSender" class="org.bigbluebutton.api.messaging.MessageSender" 
+                    init-method="start" destroy-method="stop">
+      <property name="host" value="${redisHost}" />
+      <property name="port" value="${redisPort}" />
+  	</bean>
+
+    <bean id="redisMessageReceiver" class="org.bigbluebutton.api.messaging.MessageReceiver" 
+                    init-method="start" destroy-method="stop">
+       <property name="host" value="${redisHost}" />
+       <property name="port" value="${redisPort}" />
+    	<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
+  	</bean>
+
+    <bean id="redisMessageHandler" class="org.bigbluebutton.api.messaging.ReceivedMessageHandler" 
+                    init-method="start" destroy-method="stop">
+      <property name="messageDistributor"><ref bean="redisMessageDistributor" /></property>
+    </bean>
+
+    <bean id="redisMessageDistributor" class="org.bigbluebutton.api.messaging.MessageDistributor">
+         <property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
+         <property name="messageListeners">
+            <set>
+                <ref bean="meetingMessageHandler" />
+            </set>
+        </property> 
+    </bean>
+      	
+  	<bean id="meetingMessageHandler" class="org.bigbluebutton.api.messaging.MeetingMessageHandler">
+  	     <property name="messageListeners">
+            <set>
+                <ref bean="meetingService" />
+                <ref bean="keepAliveService" />
+            </set>
+        </property> 
+  	</bean>	  	
+ 
+</beans>
diff --git a/bbb-web-api/grails-app/conf/spring/bbb-redis-pool.xml b/bbb-web-api/grails-app/conf/spring/bbb-redis-pool.xml
new file mode 100755
index 0000000000..7de4b5722c
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/bbb-redis-pool.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program 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.0 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, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:util="http://www.springframework.org/schema/util"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+			http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+			http://www.springframework.org/schema/util 
+			http://www.springframework.org/schema/util/spring-util-2.0.xsd
+			">
+  	
+
+    
+</beans>
diff --git a/bbb-web-api/grails-app/conf/spring/doc-conversion.xml b/bbb-web-api/grails-app/conf/spring/doc-conversion.xml
new file mode 100755
index 0000000000..148c479fca
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/doc-conversion.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program 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.0 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, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+			http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+			">  
+			
+	<bean id="documentConversionService" class="org.bigbluebutton.presentation.DocumentConversionServiceImp">
+		<property name="messagingService" ref="messagingService"/>
+		<property name="officeToPdfConversionService" ref="officeToPdfConversionService"/>
+		<property name="pdfToSwfSlidesGenerationService" ref="pdfToSwfSlidesGenerationService"/>
+		<property name="imageToSwfSlidesGenerationService" ref="imageToSwfSlidesGenerationService"/>
+	</bean>
+	
+	<bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"/>
+	
+	<bean id="pageExtractor" class="org.bigbluebutton.presentation.imp.GhostscriptPageExtractor">
+		<property name="ghostscriptExec" value="${ghostScriptExec}"/>
+		<property name="noPdfMarkWorkaround" value="${noPdfMarkWorkaround}"/>
+	</bean>
+	
+	<bean id="imageMagickPageConverter" class="org.bigbluebutton.presentation.imp.ImageMagickPageConverter">
+		<property name="imageMagickDir" value="${imageMagickDir}"/>
+	</bean>
+		
+	<bean id="png2SwfConverter" class="org.bigbluebutton.presentation.imp.Png2SwfPageConverter">
+		<property name="swfToolsDir" value="${swfToolsDir}"/>
+	</bean>
+	
+	<bean id="jpg2SwfConverter" class="org.bigbluebutton.presentation.imp.Jpeg2SwfPageConverter">
+		<property name="swfToolsDir" value="${swfToolsDir}"/>
+	</bean>
+		
+	<bean id="pageCounter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageCounter">
+		<property name="swfToolsDir" value="${swfToolsDir}"/>
+	</bean>
+		
+	<bean id="pageCounterService" class="org.bigbluebutton.presentation.imp.PageCounterService">
+		<property name="pageCounter" ref="pageCounter"/>
+		<property name="maxNumPages" value="${maxNumPages}"/>
+	</bean>
+	
+	<bean id="pdf2SwfPageConverter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageConverter">
+		<property name="ghostscriptExec" value="${ghostScriptExec}"/>
+		<property name="swfToolsDir" value="${swfToolsDir}"/>
+		<property name="imageMagickDir" value="${imageMagickDir}"/>
+		<property name="fontsDir" value="${fontsDir}"/>
+		<property name="noPdfMarkWorkaround" value="${noPdfMarkWorkaround}"/>
+		<property name="placementsThreshold" value="${placementsThreshold}"/>
+		<property name="defineTextThreshold" value="${defineTextThreshold}"/>
+		<property name="imageTagThreshold" value="${imageTagThreshold}"/>
+	</bean>
+	
+	<bean id="imageConvSvc" class="org.bigbluebutton.presentation.imp.PdfPageToImageConversionService">
+		<property name="pageExtractor" ref="pageExtractor"/>
+		<property name="pdfToImageConverter" ref="imageMagickPageConverter"/>
+		<property name="imageToSwfConverter" ref="png2SwfConverter"/>
+	</bean>
+	
+	<bean id="thumbCreator" class="org.bigbluebutton.presentation.imp.ThumbnailCreatorImp">
+		<property name="imageMagickDir" value="${imageMagickDir}"/>
+		<property name="blankThumbnail" value="${BLANK_THUMBNAIL}"/>
+	</bean>
+	
+	<bean id="textFileCreator" class="org.bigbluebutton.presentation.imp.TextFileCreatorImp">
+		<property name="imageMagickDir" value="${imageMagickDir}"/>
+	</bean>
+
+	<bean id="svgImageCreator" class="org.bigbluebutton.presentation.imp.SvgImageCreatorImp">
+	    <property name="imageMagickDir" value="${imageMagickDir}"/>
+	</bean>
+  	
+	<bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
+	
+	<bean id="pdfToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.PdfToSwfSlidesGenerationService">
+	   <constructor-arg index="0" value="${numConversionThreads}"/>
+		<property name="counterService" ref="pageCounterService"/>
+		<property name="pageConverter" ref="pdf2SwfPageConverter"/>
+		<property name="pdfPageToImageConversionService" ref="imageConvSvc"/>
+		<property name="thumbnailCreator" ref="thumbCreator"/>
+		<property name="textFileCreator" ref="textFileCreator"/>
+		<property name="svgImageCreator" ref="svgImageCreator"/>
+		<property name="blankSlide" value="${BLANK_SLIDE}"/>
+		<property name="maxSwfFileSize" value="${MAX_SWF_FILE_SIZE}"/>
+		<property name="maxConversionTime" value="${maxConversionTime}"/>
+		<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
+		<property name="svgImagesRequired" value="${svgImagesRequired}"/>
+	</bean>	
+	
+	<bean id="imageToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.ImageToSwfSlidesGenerationService">
+		<property name="pngPageConverter" ref="png2SwfConverter"/>
+		<property name="jpgPageConverter" ref="jpg2SwfConverter"/>
+		<property name="svgImageCreator" ref="svgImageCreator"/>
+		<property name="thumbnailCreator" ref="thumbCreator"/>
+		<property name="textFileCreator" ref="textFileCreator"/>
+		<property name="blankSlide" value="${BLANK_SLIDE}"/>
+		<property name="maxConversionTime" value="${maxConversionTime}"/>
+		<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
+	</bean>
+	
+	<bean id="swfSlidesGenerationProgressNotifier" class="org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier">
+		<property name="messagingService" ref="messagingService"/> 
+		<property name="generatedSlidesInfoHelper" ref="generatedSlidesInfoHelper"/>
+	</bean>	
+</beans>
diff --git a/bbb-web-api/grails-app/conf/spring/resources.groovy b/bbb-web-api/grails-app/conf/spring/resources.groovy
new file mode 100755
index 0000000000..fa950068bb
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/resources.groovy
@@ -0,0 +1,3 @@
+// Place your Spring DSL code here
+beans = {
+}
diff --git a/bbb-web-api/grails-app/conf/spring/resources.xml b/bbb-web-api/grails-app/conf/spring/resources.xml
new file mode 100755
index 0000000000..08e23e55d2
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/resources.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program 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.0 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, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:util="http://www.springframework.org/schema/util"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+        http://www.springframework.org/schema/util 
+        http://www.springframework.org/schema/util/spring-util-2.0.xsd">  
+
+    <bean id="messagingService" class="org.bigbluebutton.api.messaging.RedisMessagingService">
+        <property name="messageSender" ref="messageSender"/>
+        <property name="redisStorageService" ref="redisStorageService"/>
+    </bean>
+
+    <bean id="expiredMeetingCleanupTimerTask" class="org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask"/>
+
+  <bean id="registeredUserCleanupTimerTask" class="org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask"/>
+
+  <bean id="keepAliveService" class="org.bigbluebutton.web.services.KeepAliveService" 
+       init-method="start" destroy-method="stop">
+    <property name="runEvery" value="${checkBBBServerEvery}"/>
+    <property name="messagingService" ref="messagingService" />
+  </bean>
+
+    <bean id="meetingService" class="org.bigbluebutton.api.MeetingService" init-method="start" destroy-method="stop">
+        <property name="defaultMeetingExpireDuration" value="${defaultMeetingExpireDuration}"/>
+        <property name="defaultMeetingCreateJoinDuration" value="${defaultMeetingCreateJoinDuration}"/>
+        <property name="removeMeetingWhenEnded" value="${removeMeetingWhenEnded}"/>
+        <property name="expiredMeetingCleanupTimerTask" ref="expiredMeetingCleanupTimerTask"/>
+        <property name="messagingService" ref="messagingService"/>
+        <property name="recordingService" ref="recordingService"/>
+        <property name="presDownloadService" ref="presDownloadService"/>
+        <property name="paramsProcessorUtil" ref="paramsProcessorUtil"/>
+	  <property name="stunTurnService" ref="stunTurnService"/>
+  </bean>
+
+  <bean id="recordingServiceHelper" class="org.bigbluebutton.api.RecordingServiceHelperImp"/>
+  
+  <bean id="presDownloadService" class="org.bigbluebutton.presentation.PresentationUrlDownloadService">
+    <property name="presentationDir" value="${presentationDir}"/>
+    <property name="presentationBaseURL" value="${presentationBaseURL}"/>  
+    <property name="documentConversionService" ref="documentConversionService"/>  
+  </bean>
+  
+  <bean id="recordingService" class="org.bigbluebutton.api.RecordingService" >
+    <property name="recordingStatusDir" value="${recordStatusDir}"/>  
+    <property name="publishedDir" value="${publishedDir}"/>
+    <property name="unpublishedDir" value="${unpublishedDir}"/>  
+    <property name="recordingServiceHelper" ref="recordingServiceHelper"/>
+  </bean>
+  
+  <bean id="configServiceHelper" class="org.bigbluebutton.api.ClientConfigServiceHelperImp"/>
+  
+  <bean id="configService" class="org.bigbluebutton.api.ClientConfigService" init-method="init">
+    <property name="configDir" value="${configDir}"/>  
+    <property name="clientConfigServiceHelper" ref="configServiceHelper"/>
+  </bean>
+     
+  <bean id="paramsProcessorUtil" class="org.bigbluebutton.api.ParamsProcessorUtil">
+    <property name="apiVersion" value="${apiVersion}"/>
+    <property name="serviceEnabled" value="${serviceEnabled}"/>
+    <property name="securitySalt" value="${securitySalt}"/>
+    <property name="defaultMaxUsers" value="${defaultMaxUsers}"/>
+    <property name="defaultWelcomeMessage" value="${defaultWelcomeMessage}"/>
+    <property name="defaultWelcomeMessageFooter" value="${defaultWelcomeMessageFooter}"/>
+    <property name="defaultDialAccessNumber" value="${defaultDialAccessNumber}"/>
+    <property name="testVoiceBridge" value="${testVoiceBridge}"/>
+    <property name="testConferenceMock" value="${testConferenceMock}"/>
+    <property name="defaultLogoutUrl" value="${bigbluebutton.web.logoutURL}"/>
+    <property name="defaultServerUrl" value="${bigbluebutton.web.serverURL}"/>
+    <property name="defaultNumDigitsForTelVoice" value="${defaultNumDigitsForTelVoice}"/>
+    <property name="defaultClientUrl" value="${defaultClientUrl}"/>
+    <property name="defaultMeetingDuration" value="${defaultMeetingDuration}"/>
+    <property name="disableRecordingDefault" value="${disableRecordingDefault}"/>
+    <property name="autoStartRecording" value="${autoStartRecording}"/>
+    <property name="allowStartStopRecording" value="${allowStartStopRecording}"/>
+    <property name="defaultAvatarURL" value="${defaultAvatarURL}"/>
+    <property name="defaultConfigURL" value="${defaultConfigURL}"/>
+  </bean>
+        
+	<import resource="doc-conversion.xml" />
+	<import resource="bbb-redis-pool.xml" />
+	<import resource="bbb-redis-messaging.xml" />
+	<import resource="turn-stun-servers.xml" />
+</beans>
diff --git a/bbb-web-api/grails-app/conf/spring/turn-stun-servers.xml b/bbb-web-api/grails-app/conf/spring/turn-stun-servers.xml
new file mode 100755
index 0000000000..f9f80297be
--- /dev/null
+++ b/bbb-web-api/grails-app/conf/spring/turn-stun-servers.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program 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.0 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, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans
+            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+            ">
+
+    <bean id="stun1" class="org.bigbluebutton.web.services.turn.StunServer">
+        <constructor-arg index="0" value="stun:stun.freeswitch.org"/>
+    </bean>
+
+    <!--bean id="stun2" class="org.bigbluebutton.web.services.turn.StunServer">
+        <constructor-arg index="0" value="stun:stun2.example.com"/>
+    </bean-->
+
+    <!-- Turn servers are configured with a secret that's compatible with
+         http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
+         as supported by the coturn and rfc5766-turn-server turn servers -->
+
+    <!--bean id="turn1" class="org.bigbluebutton.web.services.turn.TurnServer">
+        Secret:
+        <constructor-arg index="0" value="secret"/>
+        TURN server URL, use turn: or turns:
+        <constructor-arg index="1" value="turn:turn1.example.com"/>
+        TTL in seconds for shared secret
+        <constructor-arg index="2" value="86400"/>
+    </bean-->
+
+    <!--bean id="turn2" class="org.bigbluebutton.web.services.turn.TurnServer">
+        <constructor-arg index="0" value="secret"/>
+        <constructor-arg index="1" value="turns:turn2.example.com:443"/>
+        <constructor-arg index="2" value="86400"/>
+    </bean-->
+
+    <bean id="stunTurnService" class="org.bigbluebutton.web.services.turn.StunTurnService">
+        <property name="stunServers">
+            <set>
+                <ref bean="stun1" />
+                <!--ref bean="stun2" /-->
+            </set>
+        </property>
+        <property name="turnServers">
+            <set>
+                <!--ref bean="turn1" /-->
+                <!--ref bean="turn2" /-->
+            </set>
+        </property>
+    </bean>
+</beans>
diff --git a/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
new file mode 100755
index 0000000000..f23842cf9d
--- /dev/null
+++ b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
@@ -0,0 +1,2128 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.web.controllers
+
+import org.bigbluebutton.api.responses.InvalidResponse
+
+import javax.servlet.ServletRequest;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
+import org.bigbluebutton.api.domain.Config;
+import org.bigbluebutton.api.domain.Meeting;
+import org.bigbluebutton.api.domain.Recording;
+import org.bigbluebutton.api.domain.UserSession;
+import org.bigbluebutton.api.ApiErrors;
+import org.bigbluebutton.api.ClientConfigService;
+import org.bigbluebutton.api.MeetingService;
+import org.bigbluebutton.api.ParamsProcessorUtil;
+import org.bigbluebutton.api.Util;
+import org.bigbluebutton.presentation.PresentationUrlDownloadService;
+import org.bigbluebutton.presentation.UploadedPresentation
+import org.bigbluebutton.web.services.PresentationService
+import org.bigbluebutton.web.services.turn.StunTurnService;
+import org.bigbluebutton.web.services.turn.TurnEntry;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import grails.converters.JSON
+import grails.converters.XML
+import freemarker.template.Configuration;
+import freemarker.cache.WebappTemplateLoader;
+
+class ApiController {
+  private static final Integer SESSION_TIMEOUT = 14400  // 4 hours
+  private static final String CONTROLLER_NAME = 'ApiController'
+  private static final String RESP_CODE_SUCCESS = 'SUCCESS'
+  private static final String RESP_CODE_FAILED = 'FAILED'
+  private static final String ROLE_MODERATOR = "MODERATOR";
+  private static final String ROLE_ATTENDEE = "VIEWER";
+  private static final String SECURITY_SALT = '639259d4-9dd8-4b25-bf01-95f9567eaf4b'
+  private static final String API_VERSION = '0.81'
+
+  MeetingService meetingService;
+  PresentationService presentationService
+  ParamsProcessorUtil paramsProcessorUtil
+  ClientConfigService configService
+  PresentationUrlDownloadService presDownloadService
+  StunTurnService stunTurnService
+
+  /* general methods */
+  def index = {
+    log.debug CONTROLLER_NAME + "#index"
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() {
+            returncode(RESP_CODE_SUCCESS)
+            version(paramsProcessorUtil.getApiVersion())
+          }
+        }
+      }
+    }
+  }
+
+  /*********************************** 
+   * BREAKOUT TEST (API) 
+   ***********************************/  
+  def breakout = {
+    if(!StringUtils.isEmpty(params.meetingId)) {
+      String meetingId = StringUtils.strip(params.meetingId);
+      println("MeetingId = " + meetingId)
+    } else {
+      println("Missing meetingId")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.password)) {
+      println("Missing password")
+      return
+    }
+  }
+
+  /*********************************** 
+   * CREATE (API) 
+   ***********************************/
+  def create = {
+    String API_CALL = 'create'
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+    log.debug params
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors();
+    paramsProcessorUtil.processRequiredCreateParams(params, errors);
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree with the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+
+    // Translate the external meeting id into an internal meeting id.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting existing = meetingService.getNotEndedMeetingWithId(internalMeetingId);
+    if (existing != null) {
+      log.debug "Existing conference found"
+      Map<String, Object> updateParams = paramsProcessorUtil.processUpdateCreateParams(params);
+      if (existing.getViewerPassword().equals(params.get("attendeePW")) && existing.getModeratorPassword().equals(params.get("moderatorPW"))) {
+        paramsProcessorUtil.updateMeeting(updateParams, existing);
+        // trying to create a conference a second time, return success, but give extra info
+        // Ignore pre-uploaded presentations. We only allow uploading of presentation once.
+        //uploadDocuments(existing);
+        respondWithConference(existing, "duplicateWarning", "This conference was already in existence and may currently be in progress.");
+      } else {
+        // BEGIN - backward compatibility
+        invalid("idNotUnique", "A meeting already exists with that meeting ID.  Please use a different meeting ID.");
+        return;
+        // END - backward compatibility
+
+        // enforce meetingID unique-ness
+        errors.nonUniqueMeetingIdError()
+        respondWithErrors(errors)
+      }
+
+      return;
+    }
+
+    Meeting newMeeting = paramsProcessorUtil.processCreateParams(params);
+
+    if (! StringUtils.isEmpty(params.moderatorOnlyMessage)) {
+      newMeeting.setModeratorOnlyMessage(params.moderatorOnlyMessage);
+    }
+
+    meetingService.createMeeting(newMeeting);
+
+    // See if the request came with pre-uploading of presentation.
+    uploadDocuments(newMeeting);
+    respondWithConference(newMeeting, null, null)
+  }
+
+  /**********************************************
+   * JOIN API
+   *********************************************/
+  def join = {
+    String API_CALL = 'join'
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+    ApiErrors errors = new ApiErrors()
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    //checking for an empty username or for a username containing whitespaces only
+    if(!StringUtils.isEmpty(params.fullName)) {
+      params.fullName = StringUtils.strip(params.fullName);
+      if (StringUtils.isEmpty(params.fullName)) {
+        invalid("missingParamFullName", "You must specify a name for the attendee who will be joining the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamFullName", "You must specify a name for the attendee who will be joining the meeting.");
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    if (StringUtils.isEmpty(params.password)) {
+      invalid("invalidPassword","You either did not supply a password or the password supplied is neither the attendee or moderator password for this conference.");
+      return
+    }
+
+    if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    // END - backward compatibility
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a name for the user joining? If none, complain.
+    if(!StringUtils.isEmpty(params.fullName)) {
+      params.fullName = StringUtils.strip(params.fullName);
+      if (StringUtils.isEmpty(params.fullName)) {
+        errors.missingParamError("fullName");
+      }
+    } else {
+      errors.missingParamError("fullName");
+    }
+    String fullName = params.fullName
+
+    // Do we have a meeting id? If none, complain.
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        errors.missingParamError("meetingID");
+      }
+    }
+    else {
+      errors.missingParamError("meetingID");
+    }
+    String externalMeetingId = params.meetingID
+
+    // Do we have a password? If not, complain.
+    String attPW = params.password
+    if (StringUtils.isEmpty(attPW)) {
+      errors.missingParamError("password");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    Boolean isBreakoutRoom = false
+    if(!StringUtils.isEmpty(params.isBreakout)) {
+      isBreakoutRoom = new Boolean(StringUtils.strip(params.isBreakout))
+    }
+
+    // Everything is good so far. Translate the external meeting id to an internal meeting id. If
+    // we can't find the meeting, complain.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
+    if (isBreakoutRoom) {
+      // This is a join request for a breakout room. Use the passed meetingId to find the meeting.
+      internalMeetingId = externalMeetingId
+      log.info("Join request for breakout room " + internalMeetingId)
+    }
+
+    log.info("Retrieving meeting ${internalMeetingId}")
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+
+      errors.invalidMeetingIdError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    // the createTime mismatch with meeting's createTime, complain
+    // In the future, the createTime param will be required
+    if (params.createTime != null) {
+      long createTime = 0;
+      try{
+        createTime=Long.parseLong(params.createTime);
+      } catch(Exception e){
+        log.warn("could not parse createTime param");
+        createTime = -1;
+      }
+      if(createTime != meeting.getCreateTime()) {
+        errors.mismatchCreateTimeParam();
+        respondWithErrors(errors);
+        return;
+      }
+    }
+
+    // Is this user joining a meeting that has been ended. If so, complain.
+    if (meeting.isForciblyEnded()) {
+      // BEGIN - backward compatibility
+      invalid("meetingForciblyEnded", "You can not re-join a meeting that has already been forcibly ended.  However, once the meeting is removed from memory (according to the timeout configured on this server, you will be able to once again create a meeting with the same meeting ID");
+      return;
+      // END - backward compatibility
+
+      errors.meetingForciblyEndedError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    // Now determine if this user is a moderator or a viewer.
+    String role = null;
+    if (meeting.getModeratorPassword().equals(attPW)) {
+      role = ROLE_MODERATOR;
+    } else if (meeting.getViewerPassword().equals(attPW)) {
+      role = ROLE_ATTENDEE;
+    }
+
+    if (role == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidPassword","You either did not supply a password or the password supplied is neither the attendee or moderator password for this conference.");
+      return
+      // END - backward compatibility
+
+      errors.invalidPasswordError()
+      respondWithErrors(errors)
+      return;
+    }
+
+    String webVoice = StringUtils.isEmpty(params.webVoiceConf) ? meeting.getTelVoice() : params.webVoiceConf
+
+    boolean redirectImm = parseBoolean(params.redirectImmediately)
+
+    String internalUserID = RandomStringUtils.randomAlphanumeric(12).toLowerCase()
+
+    String authToken = RandomStringUtils.randomAlphanumeric(12).toLowerCase()
+    
+    String sessionToken = RandomStringUtils.randomAlphanumeric(16).toLowerCase()
+
+    String externUserID = params.userID
+    if (StringUtils.isEmpty(externUserID)) {
+      externUserID = internalUserID
+    }
+
+    //Return a Map with the user custom data
+    Map<String,String> userCustomData = paramsProcessorUtil.getUserCustomData(params);
+
+    //Currently, it's associated with the externalUserID
+    if (userCustomData.size() > 0)
+      meetingService.addUserCustomData(meeting.getInternalId(), externUserID, userCustomData);
+
+    String configxml = null;
+
+    if (! StringUtils.isEmpty(params.configToken)) {
+      Config conf = meeting.getConfig(params.configToken);
+      if (conf == null) {
+        // Check if this config is one of our pre-built config
+        configxml = configService.getConfig(params.configToken)
+        if (configxml == null) {
+          // Default to the default config.
+          configxml = conf.config;
+        }
+      } else {
+        configxml = conf.config;
+      }
+    } else {
+      Config conf = meeting.getDefaultConfig();
+      if (conf == null) {
+        errors.noConfigFound();
+        respondWithErrors(errors);
+      } else {
+        configxml = conf.config;
+      }
+    }
+
+    if (StringUtils.isEmpty(configxml)) {
+      errors.noConfigFound();
+      respondWithErrors(errors);
+    }
+    UserSession us = new UserSession();
+    us.authToken = authToken;
+    us.internalUserId = internalUserID
+    us.conferencename = meeting.getName()
+    us.meetingID = meeting.getInternalId()
+    us.externMeetingID = meeting.getExternalId()
+    us.externUserID = externUserID
+    us.fullname = fullName
+    us.role = role
+    us.conference = meeting.getInternalId()
+    us.room = meeting.getInternalId()
+    us.voicebridge = meeting.getTelVoice()
+    us.webvoiceconf = meeting.getWebVoice()
+    us.mode = "LIVE"
+    us.record = meeting.isRecord()
+    us.welcome = meeting.getWelcomeMessage()
+    us.logoutUrl = meeting.getLogoutUrl();
+    us.configXML = configxml;
+
+    if (! StringUtils.isEmpty(params.defaultLayout)) {
+      us.defaultLayout = params.defaultLayout;
+    }
+
+    if (! StringUtils.isEmpty(params.avatarURL)) {
+      us.avatarURL = params.avatarURL;
+    } else {
+      us.avatarURL = meeting.defaultAvatarURL
+    }
+
+    session[sessionToken] = sessionToken
+    meetingService.addUserSession(sessionToken, us);
+
+    // Register user into the meeting.
+    meetingService.registerUser(us.meetingID, us.internalUserId, us.fullname, us.role, us.externUserID, us.authToken, us.avatarURL)
+
+    log.info("Session user token for " + us.fullname + " [" + session[sessionToken]+ "]")
+    session.setMaxInactiveInterval(SESSION_TIMEOUT);
+
+    //check if exists the param redirect
+    boolean redirectClient = true;
+    String clientURL = paramsProcessorUtil.getDefaultClientUrl();
+
+    if(! StringUtils.isEmpty(params.redirect)) {
+      try{
+        redirectClient = Boolean.parseBoolean(params.redirect);
+      }catch(Exception e){
+        redirectClient = true;
+      }
+    }
+
+    if(!StringUtils.isEmpty(params.clientURL)){
+      clientURL = params.clientURL;
+    }
+
+    if (redirectClient){
+      String destUrl = clientURL + "?sessionToken=" + sessionToken
+      log.info("Successfully joined. Redirecting to ${destUrl}");
+      redirect(url: destUrl);
+    }
+    else{
+      log.info("Successfully joined. Sending XML response.");
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              messageKey("successfullyJoined")
+              message("You have joined successfully.")
+              meeting_id() { mkp.yield(us.meetingID) }
+              user_id(us.internalUserId)
+              auth_token(us.authToken)
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /*******************************************
+   * IS_MEETING_RUNNING API
+   *******************************************/
+  def isMeetingRunning = {
+    String API_CALL = 'isMeetingRunning'
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a meeting id? If none, complain.
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        errors.missingParamError("meetingID");
+      }
+    } else {
+      errors.missingParamError("meetingID");
+    }
+    String externalMeetingId = params.meetingID
+
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    // Everything is good so far. Translate the external meeting id to an internal meeting id. If
+    // we can't find the meeting, complain.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
+    log.info("Retrieving meeting ${internalMeetingId}")
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    boolean isRunning = meeting != null && meeting.isRunning();
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() {
+            returncode(RESP_CODE_SUCCESS)
+            running(isRunning ? "true" : "false")
+          }
+        }
+      }
+    }
+  }
+
+  /************************************
+   * END API
+   ************************************/
+  def end = {
+    String API_CALL = "end"
+
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    if (StringUtils.isEmpty(params.password)) {
+      invalid("invalidPassword","You must supply the moderator password for this call.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a meeting id? If none, complain.
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        errors.missingParamError("meetingID");
+      }
+    } else {
+      errors.missingParamError("meetingID");
+    }
+    String externalMeetingId = params.meetingID
+
+    // Do we have a password? If not, complain.
+    String modPW = params.password
+    if (StringUtils.isEmpty(modPW)) {
+      errors.missingParamError("password");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    // Everything is good so far. Translate the external meeting id to an internal meeting id. If
+    // we can't find the meeting, complain.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
+    log.info("Retrieving meeting ${internalMeetingId}")
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("notFound", "We could not find a meeting with that meeting ID - perhaps the meeting is not yet running?");
+      return;
+      // END - backward compatibility
+
+      errors.invalidMeetingIdError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    if (meeting.getModeratorPassword().equals(modPW) == false) {
+      // BEGIN - backward compatibility
+      invalid("invalidPassword","You must supply the moderator password for this call.");
+      return;
+      // END - backward compatibility
+
+      errors.invalidPasswordError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    meetingService.endMeeting(meeting.getInternalId());
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() {
+            returncode(RESP_CODE_SUCCESS)
+            messageKey("sentEndMeetingRequest")
+            message("A request to end the meeting was sent.  Please wait a few seconds, and then use the getMeetingInfo or isMeetingRunning API calls to verify that it was ended.")
+          }
+        }
+      }
+    }
+  }
+
+  /*****************************************
+   * GETMEETINGINFO API
+   *****************************************/
+  def getMeetingInfo = {
+    String API_CALL = "getMeetingInfo"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a meeting id? If none, complain.
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        errors.missingParamError("meetingID");
+      }
+    } else {
+      errors.missingParamError("meetingID");
+    }
+    String externalMeetingId = params.meetingID
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    // Everything is good so far. Translate the external meeting id to an internal meeting id. If
+    // we can't find the meeting, complain.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
+    log.info("Retrieving meeting ${internalMeetingId}")
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("notFound", "We could not find a meeting with that meeting ID");
+      return;
+      // END - backward compatibility
+
+      errors.invalidMeetingIdError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    respondWithConferenceDetails(meeting, null, null, null);
+  }
+
+  /************************************
+   *  GETMEETINGS API
+   ************************************/
+  def getMeetingsHandler = {
+    String API_CALL = "getMeetings"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    Collection<Meeting> mtgs = meetingService.getMeetings();
+
+    if (mtgs == null || mtgs.isEmpty()) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              meetings()
+              messageKey("noMeetings")
+              message("no meetings were found on this server")
+            }
+          }
+        }
+      }
+    } else {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              meetings {
+                for (m in mtgs) {
+                  meeting {
+                    meetingID() { mkp.yield(m.getExternalId()) }
+                    isBreakout() { mkp.yield(m.isBreakout()) }
+                    meetingName() { mkp.yield(m.getName()) }
+                    createTime(m.getCreateTime())
+                    createDate(formatPrettyDate(m.getCreateTime()))
+                    voiceBridge() { mkp.yield(m.getTelVoice()) }
+                    dialNumber() { mkp.yield(m.getDialNumber()) }
+                    attendeePW() { mkp.yield(m.getViewerPassword()) }
+                    moderatorPW() { mkp.yield(m.getModeratorPassword()) }
+                    hasBeenForciblyEnded(m.isForciblyEnded() ? "true" : "false")
+                    running(m.isRunning() ? "true" : "false")
+                    participantCount(m.getNumUsers())
+                    listenerCount(m.getNumListenOnly())
+                    voiceParticipantCount(m.getNumVoiceJoined())
+                    videoCount(m.getNumVideos())
+                    duration(m.duration)
+                    hasUserJoined(m.hasUserJoined())
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /************************************
+   *  GETSESSIONS API
+   ************************************/
+  def getSessionsHandler = {
+    String API_CALL = "getSessions"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    Collection<Meeting> sssns = meetingService.getSessions();
+
+    if (sssns == null || sssns.isEmpty()) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              sessions()
+              messageKey("noSessions")
+              message("no sessions were found on this server")
+            }
+          }
+        }
+      }
+    } else {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              sessions {
+                for (m in sssns) {
+                  meeting {
+                    meetingID() { mkp.yield(m.meetingID) }
+                    meetingName() { mkp.yield(m.conferencename) }
+                    userName() { mkp.yield(m.fullname) }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  
+  private Map<String, String[]> getParameters(ServletRequest request) {
+    // Copy the parameters into our own Map as we can't pass the paramMap
+    // from the request as it's an unmodifiable map.
+    Map<String, String[]> reqParams = new HashMap<String, String[]>();
+    Map<String, String[]> unModReqParams = request.getParameterMap();
+
+    SortedSet<String> keys = new TreeSet<String>(unModReqParams.keySet());
+
+    for (String key: keys) {
+      reqParams.put(key, unModReqParams.get(key));
+    }
+
+    return reqParams;
+  }
+
+  /***********************************************
+   * POLL API
+   ***********************************************/
+  def setPollXML = {
+    String API_CALL = "setPollXML"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.pollXML)) {
+      invalid("configXMLError", "You did not pass a poll XML")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    // Translate the external meeting id into an internal meeting id.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+    }
+
+    Map<String, String[]> reqParams = getParameters(request)
+
+    String pollXML = params.pollXML
+
+    String decodedPollXML;
+
+    try {
+      decodedPollXML = URLDecoder.decode(pollXML, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      log.error("Couldn't decode poll XML.");
+      invalid("pollXMLError", "Cannot decode poll XML")
+      return;
+    }
+
+    if (! paramsProcessorUtil.isPostChecksumSame(API_CALL, reqParams)) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("FAILED")
+              messageKey("pollXMLChecksumError")
+              message("pollXMLChecksumError: request did not pass the checksum security check.")
+            }
+          }
+        }
+      }
+    } else {
+
+      def pollxml = new XmlSlurper().parseText(decodedPollXML);
+
+      pollxml.children().each { poll ->
+        String title = poll.title.text();
+        String question = poll.question.text();
+        String questionType = poll.questionType.text();
+
+        ArrayList<String> answers = new ArrayList<String>();
+        poll.answers.children().each { answer ->
+          answers.add(answer.text());
+        }
+
+        //send poll to BigBlueButton Apps
+        meetingService.createdPolls(meeting.getInternalId(), title, question, questionType, answers);
+      }
+
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() { returncode("SUCCESS") }
+          }
+        }
+      }
+    }
+  }
+
+  /***********************************************
+   * CONFIG API
+   ***********************************************/
+  def setConfigXML = {
+    String API_CALL = "setConfigXML"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.configXML)) {
+      invalid("configXMLError", "You did not pass a config XML")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    // Translate the external meeting id into an internal meeting id.
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+    }
+
+    Map<String, String[]> reqParams = getParameters(request)
+
+    String configXML = params.configXML
+
+    String decodedConfigXML;
+
+    try {
+      decodedConfigXML = URLDecoder.decode(configXML, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      log.error("Couldn't decode config XML.");
+      invalid("configXMLError", "Cannot decode config XML")
+      return;
+    }
+
+    if (! paramsProcessorUtil.isPostChecksumSame(API_CALL, reqParams)) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("FAILED")
+              messageKey("configXMLChecksumError")
+              message("configXMLChecksumError: request did not pass the checksum security check.")
+            }
+          }
+        }
+      }
+    } else {
+      boolean defaultConfig = false;
+
+      if (! StringUtils.isEmpty(params.defaultConfig)) {
+        try {
+          defaultConfig = Boolean.parseBoolean(params.defaultConfig);
+        } catch(Exception e) {
+          defaultConfig = false;
+        }
+      }
+
+      String token = meeting.storeConfig(defaultConfig, decodedConfigXML);
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("SUCCESS")
+              configToken(token)
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /***********************************************
+   * CALLBACK API
+   ***********************************************/
+  def subscribeEvent = {
+    String API_CALL = "subscribeEvent"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.callbackURL)) {
+      invalid("missingParamCallbackURL", "You must specify a callbackURL for subscribing");
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+
+      errors.invalidMeetingIdError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("FAILED")
+              messageKey("subscribeEventChecksumError")
+              message("subscribeEventChecksumError: request did not pass the checksum security check.")
+            }
+          }
+        }
+      }
+    } else {
+      String sid = meetingService.addSubscription(meeting.getInternalId(), meeting.getExternalId(), params.callbackURL);
+
+      if(sid.isEmpty()){
+        response.addHeader("Cache-Control", "no-cache")
+        withFormat {
+          xml {
+            render(contentType:"text/xml") {
+              response() {
+                returncode("FAILED")
+                messageKey("subscribeEventError")
+                message("subscribeEventError: An error happen while storing your subscription. Check the logs.")
+              }
+            }
+          }
+        }
+
+      }else{
+        response.addHeader("Cache-Control", "no-cache")
+        withFormat {
+          xml {
+            render(contentType:"text/xml") {
+              response() {
+                returncode("SUCCESS")
+                subscriptionID(sid)
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  def unsubscribeEvent = {
+    String API_CALL = "unsubscribeEvent"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.subscriptionID)) {
+      invalid("missingParamSubscriptionID", "You must pass a subscriptionID for unsubscribing")
+      return
+    }
+
+    if(!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+
+      errors.invalidMeetingIdError();
+      respondWithErrors(errors)
+      return;
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("FAILED")
+              messageKey("unsubscribeEventChecksumError")
+              message("unsubscribeEventChecksumError: request did not pass the checksum security check.")
+            }
+          }
+        }
+      }
+    } else {
+      boolean status = meetingService.removeSubscription(meeting.getInternalId(), params.subscriptionID);
+
+      if(!status){
+        response.addHeader("Cache-Control", "no-cache")
+        withFormat {
+          xml {
+            render(contentType:"text/xml") {
+              response() {
+                returncode("FAILED")
+                messageKey("unsubscribeEventError")
+                message("unsubscribeEventError: An error happen while unsubscribing. Check the logs.")
+              }
+            }
+          }
+        }
+
+      }else{
+        response.addHeader("Cache-Control", "no-cache")
+        withFormat {
+          xml {
+            render(contentType:"text/xml") {
+              response() {
+                returncode("SUCCESS")
+                unsubscribed(status)
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  def listSubscriptions = {
+    String API_CALL = "listSubscriptions"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (!StringUtils.isEmpty(params.meetingID)) {
+      params.meetingID = StringUtils.strip(params.meetingID);
+      if (StringUtils.isEmpty(params.meetingID)) {
+        invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+        return
+      }
+    } else {
+      invalid("missingParamMeetingID", "You must specify a meeting ID for the meeting.");
+      return
+    }
+
+    String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(params.meetingID);
+    Meeting meeting = meetingService.getMeeting(internalMeetingId);
+    if (meeting == null) {
+      // BEGIN - backward compatibility
+      invalid("invalidMeetingIdentifier", "The meeting ID that you supplied did not match any existing meetings");
+      return;
+      // END - backward compatibility
+    }
+
+    if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType: "text/xml") {
+            response() {
+              returncode("FAILED")
+              messageKey("listSubscriptionsChecksumError")
+              message("listSubscriptionsChecksumError: request did not pass the checksum security check.")
+            }
+          }
+        }
+      }
+    } else {
+      List<Map<String, String>> list = meetingService.listSubscriptions(meeting.getInternalId());
+
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType: "text/xml") {
+            response() {
+              returncode("SUCCESS")
+              subscriptions() {
+                list.each { item ->
+                  subscription() {
+                    subscriptionID() { mkp.yield(item.get("subscriptionID")) }
+                    event() { mkp.yield(item.get("event")) }
+                    callbackURL() { mkp.yield(item.get("callbackURL")) }
+                    active() { mkp.yield(item.get("active")) }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+    def getDefaultConfigXML = {
+
+        String API_CALL = "getDefaultConfigXML"
+        ApiErrors errors = new ApiErrors();
+
+        if (StringUtils.isEmpty(params.checksum)) {
+            invalid("checksumError", "You did not pass the checksum security check")
+            return
+        }
+
+        // Do we agree on the checksum? If not, complain.
+        if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+            errors.checksumError()
+            respondWithErrors(errors)
+            return
+        }
+
+        String defConfigXML = paramsProcessorUtil.getDefaultConfigXML();
+
+        response.addHeader("Cache-Control", "no-cache")
+        render text: defConfigXML, contentType: 'text/xml'
+    }
+
+
+  /***********************************************
+   * CONFIG API
+   ***********************************************/
+  def configXML = {
+    String API_CALL = 'configXML'
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
+    boolean reject = false
+    String sessionToken = null
+    UserSession us = null
+
+    if (StringUtils.isEmpty(params.sessionToken)) {
+      log.info("No session for user in conference.")
+      reject = true
+    } else {
+      sessionToken = StringUtils.strip(params.sessionToken)
+      log.info("SessionToken = " + sessionToken)
+      if (!session[sessionToken]) {
+          reject = true
+      } else {
+          us = meetingService.getUserSession(sessionToken);
+          if (us == null) reject = true
+      }
+    }
+
+    if (reject) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode("FAILED")
+              message("Could not find conference.")
+              logoutURL() { mkp.yield(logoutUrl) }
+            }
+          }
+        }
+      }
+    } else {
+      response.addHeader("Cache-Control", "no-cache")
+      render text: us.configXML, contentType: 'text/xml'
+    }
+  }
+
+  /***********************************************
+   * ENTER API
+   ***********************************************/
+  def enter = {
+    boolean reject = false;
+    
+    if (StringUtils.isEmpty(params.sessionToken)) {
+      println("SessionToken is missing.")
+    }
+    
+    String sessionToken = StringUtils.strip(params.sessionToken)
+
+    UserSession us = null;
+    Meeting meeting = null;
+
+    if (!session[sessionToken]) {
+      reject = true;
+    } else {
+      if (meetingService.getUserSession(sessionToken) == null)
+        reject = true;
+      else {
+        us = meetingService.getUserSession(sessionToken);
+        meeting = meetingService.getMeeting(us.meetingID, true);
+        if (meeting == null || meeting.isForciblyEnded()) {
+          reject = true
+        }
+      }
+    }
+
+    if (reject) {
+      log.info("No session for user in conference.")
+
+      // Determine the logout url so we can send the user there.
+      String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
+
+      if (us != null) {
+        logoutUrl = us.logoutUrl
+      }
+
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        json {
+          render(contentType: "application/json") {
+            response = {
+              returncode = "FAILED"
+              message = "Could not find conference."
+              logoutURL = logoutUrl
+            }
+          }
+        }
+      }
+    } else {
+
+      Map<String,String> userCustomData = paramsProcessorUtil.getUserCustomData(params);
+
+      // Generate a new userId for this user. This prevents old connections from
+      // removing the user when the user reconnects after being disconnected. (ralam jan 22, 2015)
+      // We use underscore (_) to associate userid with the user. We are also able to track
+      // how many times a user reconnects or refresh the browser.
+      String newInternalUserID = us.internalUserId + "_" + us.incrementConnectionNum()
+
+      log.info("Found conference for " + us.fullname)
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        json {
+          render(contentType: "application/json") {
+            response = {
+              returncode = "SUCCESS"
+              fullname = us.fullname
+              confname = us.conferencename
+              meetingID = us.meetingID
+              externMeetingID = us.externMeetingID
+              externUserID = us.externUserID
+              internalUserID = newInternalUserID
+              authToken = us.authToken
+              role = us.role
+              conference = us.conference
+              room = us.room
+              voicebridge = us.voicebridge
+              dialnumber = meeting.getDialNumber()
+              webvoiceconf = us.webvoiceconf
+              mode = us.mode
+              record = us.record
+              isBreakout = meeting.isBreakout()
+              allowStartStopRecording = meeting.getAllowStartStopRecording()
+              welcome = us.welcome
+              if (! StringUtils.isEmpty(meeting.moderatorOnlyMessage))
+                modOnlyMessage = meeting.moderatorOnlyMessage
+              logoutUrl = us.logoutUrl
+              defaultLayout = us.defaultLayout
+              avatarURL = us.avatarURL
+              customdata = array {
+                userCustomData.each { k, v ->
+                  // Somehow we need to prepend something (custdata) for the JSON to work
+                  custdata "$k" : v
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /***********************************************
+   * STUN/TURN API
+   ***********************************************/
+  def stuns = {
+    boolean reject = false;
+
+    UserSession us = null;
+    Meeting meeting = null;
+    String sessionToken = null
+    
+    if (!StringUtils.isEmpty(params.sessionToken)) {
+      sessionToken = StringUtils.strip(params.sessionToken)
+      println("Session token = [" + sessionToken + "]")
+    }
+    
+    if (!session[sessionToken]) {
+      reject = true;
+    } else {
+      if (meetingService.getUserSession(session[sessionToken]) == null)
+        reject = true;
+      else {
+        us = meetingService.getUserSession(session[sessionToken]);
+        meeting = meetingService.getMeeting(us.meetingID);
+        if (meeting == null || meeting.isForciblyEnded()) {
+          reject = true
+        }
+      }
+    }
+
+    if (reject) {
+      log.info("No session for user in conference.")
+
+      String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
+
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        json {
+          render(contentType: "application/json") {
+            response = {
+              returncode = "FAILED"
+              message = "Could not find conference."
+              logoutURL = logoutUrl
+            }
+          }
+        }
+      }
+    } else {
+      Set<String> stuns = stunTurnService.getStunServers()
+      Set<TurnEntry> turns = stunTurnService.getStunAndTurnServersFor(us.internalUserId)
+
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        json {
+          render(contentType: "application/json") {
+            stunServers = array {
+              stuns.each { stun ->
+                stunData = { url = stun.url }
+              }
+            }
+            turnServers = array {
+              turns.each { turn ->
+                turnData = {
+                  username = turn.username
+                  password = turn.password
+                  url = turn.url
+                  ttl = turn.ttl
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+  /*************************************************
+   * SIGNOUT API
+   *************************************************/
+  def signOut = {
+
+    String sessionToken = null
+
+    if (! StringUtils.isEmpty(params.sessionToken)) {
+      sessionToken = StringUtils.strip(params.sessionToken)
+      println("SessionToken = " + sessionToken)
+    }
+
+    Meeting meeting = null;
+
+    if (sessionToken != null) {
+      log.info("Found session for user in conference.")
+      UserSession us = meetingService.removeUserSession(sessionToken);
+      session.removeAttribute(sessionToken)
+    }
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() { returncode(RESP_CODE_SUCCESS) }
+        }
+      }
+    }
+  }
+
+  /******************************************************
+   * GET_RECORDINGS API
+   ******************************************************/
+  def getRecordingsHandler = {
+    String API_CALL = "getRecordings"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    List<String> externalMeetingIds = new ArrayList<String>();
+    if (!StringUtils.isEmpty(params.meetingID)) {
+      externalMeetingIds=paramsProcessorUtil.decodeIds(params.meetingID);
+    }
+
+    List<String> internalRecordIds = new ArrayList<String>()
+    if (!StringUtils.isEmpty(params.recordID)) {
+      internalRecordIds = paramsProcessorUtil.decodeIds(params.recordID)
+    }
+
+    List<String> states = new ArrayList<String>()
+    if (!StringUtils.isEmpty(params.state)) {
+      states = paramsProcessorUtil.decodeIds(params.state)
+    }
+
+    // Everything is good so far.
+    if ( internalRecordIds.size() == 0 && externalMeetingIds.size() > 0 ) {
+      // No recordIDs, process the request based on meetingID(s)
+      // Translate the external meeting ids to internal meeting ids (which is the seed for the recordIDs).
+      internalRecordIds = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingIds);
+    }
+
+    Map<String,Recording> recs = meetingService.getRecordings(internalRecordIds, states);
+    recs = meetingService.filterRecordingsByMetadata(recs, ParamsProcessorUtil.processMetaParam(params));
+
+    if (recs.isEmpty()) {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            response() {
+              returncode(RESP_CODE_SUCCESS)
+              recordings(null)
+              messageKey("noRecordings")
+              message("There are not recordings for the meetings")
+            }
+          }
+        }
+      }
+      return;
+    }
+    def cfg = new Configuration()
+
+    // Load the XML template
+    // TODO: Maybe there is a better way to define the templates path
+    def wtl = new WebappTemplateLoader(getServletContext(), "/WEB-INF/freemarker")
+    cfg.setTemplateLoader(wtl)
+    def ftl = cfg.getTemplate("get-recordings.ftl")
+    def xmlText = new StringWriter()
+    ftl.process([code:RESP_CODE_SUCCESS, recs:recs.values()], xmlText)
+    withFormat {
+      xml {
+        render(text: xmlText.toString(), contentType: "text/xml")
+      }
+    }
+  }
+
+  /******************************************************
+   * PUBLISH_RECORDINGS API
+   ******************************************************/
+
+  def publishRecordings = {
+    String API_CALL = "publishRecordings"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.recordID)) {
+      invalid("missingParamRecordID", "You must specify a recordID.");
+      return
+    }
+
+    if (StringUtils.isEmpty(params.publish)) {
+      invalid("missingParamPublish", "You must specify a publish value true or false.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a recording id? If none, complain.
+    String recordId = params.recordID
+    if (StringUtils.isEmpty(recordId)) {
+      errors.missingParamError("recordID");
+    }
+    // Do we have a publish status? If none, complain.
+    String publish = params.publish
+    if (StringUtils.isEmpty(publish)) {
+      errors.missingParamError("publish");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    List<String> recordIdList = new ArrayList<String>();
+    if (!StringUtils.isEmpty(recordId)) {
+      recordIdList=paramsProcessorUtil.decodeIds(recordId);
+    }
+
+    if(!meetingService.existsAnyRecording(recordIdList)){
+      // BEGIN - backward compatibility
+      invalid("notFound", "We could not find recordings");
+      return;
+      // END - backward compatibility
+
+    }
+
+    meetingService.setPublishRecording(recordIdList,publish.toBoolean());
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() {
+            returncode(RESP_CODE_SUCCESS)
+            published(publish)
+          }
+        }
+      }
+    }
+  }
+
+  /******************************************************
+   * DELETE_RECORDINGS API
+   ******************************************************/
+  def deleteRecordings = {
+    String API_CALL = "deleteRecordings"
+    log.debug CONTROLLER_NAME + "#${API_CALL}"
+
+    // BEGIN - backward compatibility
+    if (StringUtils.isEmpty(params.checksum)) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+
+    if (StringUtils.isEmpty(params.recordID)) {
+      invalid("missingParamRecordID", "You must specify a recordID.");
+      return
+    }
+
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      invalid("checksumError", "You did not pass the checksum security check")
+      return
+    }
+    // END - backward compatibility
+
+    ApiErrors errors = new ApiErrors()
+
+    // Do we have a checksum? If none, complain.
+    if (StringUtils.isEmpty(params.checksum)) {
+      errors.missingParamError("checksum");
+    }
+
+    // Do we have a recording id? If none, complain.
+    String recordId = params.recordID
+    if (StringUtils.isEmpty(recordId)) {
+      errors.missingParamError("recordID");
+    }
+
+    if (errors.hasErrors()) {
+      respondWithErrors(errors)
+      return
+    }
+
+    // Do we agree on the checksum? If not, complain.
+    if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
+      errors.checksumError()
+      respondWithErrors(errors)
+      return
+    }
+
+    ArrayList<String> recordIdList = new ArrayList<String>();
+    if (!StringUtils.isEmpty(recordId)) {
+      recordIdList=paramsProcessorUtil.decodeIds(recordId);
+    }
+
+    if(recordIdList.isEmpty()){
+      // BEGIN - backward compatibility
+      invalid("notFound", "We could not find recordings");
+      return;
+      // END - backward compatibility
+    }
+
+    meetingService.deleteRecordings(recordIdList);
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response() {
+            returncode(RESP_CODE_SUCCESS)
+            deleted(true)
+          }
+        }
+      }
+    }
+  }
+
+  def uploadDocuments(conf) {
+    log.debug("ApiController#uploadDocuments(${conf.getInternalId()})");
+
+    String requestBody = request.inputStream == null ? null : request.inputStream.text;
+    requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody;
+
+    if (requestBody == null) {
+      downloadAndProcessDocument(presentationService.defaultUploadedPresentation, conf.getInternalId());
+    } else {
+      log.debug "Request body: \n" + requestBody;
+      def xml = new XmlSlurper().parseText(requestBody);
+      xml.children().each { module ->
+        log.debug("module config found: [${module.@name}]");
+
+        if ("presentation".equals(module.@name.toString())) {
+          // need to iterate over presentation files and process them
+          module.children().each { document ->
+            if (!StringUtils.isEmpty(document.@url.toString())) {
+              downloadAndProcessDocument(document.@url.toString(), conf.getInternalId());
+            } else if (!StringUtils.isEmpty(document.@name.toString())) {
+              def b64 = new Base64()
+              def decodedBytes = b64.decode(document.text().getBytes())
+              processDocumentFromRawBytes(decodedBytes, document.@name.toString(), conf.getInternalId());
+            } else {
+              log.debug("presentation module config found, but it did not contain url or name attributes");
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+  def processDocumentFromRawBytes(bytes, presFilename, meetingId) {
+    def filenameExt = Util.getFilenameExt(presFilename);
+    String presentationDir = presentationService.getPresentationDir()
+    def presId = Util.generatePresentationId(presFilename)
+    File uploadDir = Util.createPresentationDirectory(meetingId, presentationDir, presId)
+    if (uploadDir != null) {
+      def newFilename = Util.createNewFilename(presId, filenameExt)
+      def pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename);
+
+      FileOutputStream fos = new java.io.FileOutputStream(pres)
+      fos.write(bytes)
+      fos.flush()
+      fos.close()
+
+      processUploadedFile(meetingId, presId, presFilename, pres);
+    }
+
+  }
+
+  def downloadAndProcessDocument(address, meetingId) {
+    log.debug("ApiController#downloadAndProcessDocument(${address}, ${meetingId})");
+    String presFilename = address.tokenize("/")[-1];
+    def filenameExt = presDownloadService.getFilenameExt(presFilename);
+    String presentationDir = presentationService.getPresentationDir()
+
+    def presId = presDownloadService.generatePresentationId(presFilename)
+    File uploadDir = presDownloadService.createPresentationDirectory(meetingId, presentationDir, presId)
+    if (uploadDir != null) {
+      def newFilename = presDownloadService.createNewFilename(presId, filenameExt)
+      def newFilePath = uploadDir.absolutePath + File.separatorChar + newFilename
+
+      if (presDownloadService.savePresentation(meetingId, newFilePath, address)) {
+        def pres = new File(newFilePath)
+        processUploadedFile(meetingId, presId, presFilename, pres);
+      } else {
+        log.error("Failed to download presentation=[${address}], meeting=[${meetingId}]")
+      }
+    }
+  }
+
+
+  def processUploadedFile(meetingId, presId, filename, presFile) {
+    def presentationBaseUrl = presentationService.presentationBaseUrl
+    UploadedPresentation uploadedPres = new UploadedPresentation(meetingId, presId, filename, presentationBaseUrl);
+    uploadedPres.setUploadedFile(presFile);
+    presentationService.processUploadedPresentation(uploadedPres);
+  }
+
+  def beforeInterceptor = {
+    if (paramsProcessorUtil.isServiceEnabled() == false) {
+      log.info("apiNotEnabled: The API service and/or controller is not enabled on this server.  To use it, you must first enable it.")
+      // TODO: this doesn't stop the request - so it generates invalid XML
+      //      since the request continues and renders a second response
+      invalid("apiNotEnabled", "The API service and/or controller is not enabled on this server.  To use it, you must first enable it.")
+    }
+  }
+
+  def formatPrettyDate(timestamp) {
+    //    SimpleDateFormat ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
+    //    return ft.format(new Date(timestamp))
+
+    return new Date(timestamp).toString()
+  }
+
+  def respondWithConferenceDetails(meeting, room, msgKey, msg) {
+    def createdOn = formatPrettyDate(meeting.getCreateTime())
+
+    def messageKey = ""
+    if (!StringUtils.isEmpty(msgKey)) {
+      messageKey = msgKey
+    }
+
+    def message = ""
+    if (!StringUtils.isEmpty(msg)) {
+      message = msg
+    }
+
+    def cfg = new Configuration()
+    // Load the XML template
+    // TODO: Maybe there is a better way to define the templates path
+    def wtl = new WebappTemplateLoader(getServletContext(), "/WEB-INF/freemarker")
+    cfg.setTemplateLoader(wtl)
+    def ftl = cfg.getTemplate("respond-with-conference-details.ftl")
+    def xmlText = new StringWriter()
+    ftl.process([returnCode: RESP_CODE_SUCCESS, messageKey: messageKey,
+                 message: message, createdOn: createdOn, meeting: meeting], xmlText)
+
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(text: xmlText.toString(), contentType: "text/xml")
+      }
+    }
+  }
+
+  def respondWithConference(meeting, msgKey, msg) {
+
+    def createdOn = formatPrettyDate(meeting.getCreateTime())
+
+    def messageKey = ""
+    if (!StringUtils.isEmpty(msgKey)) {
+      messageKey = msgKey
+    }
+
+    def message = ""
+    if (!StringUtils.isEmpty(msg)) {
+      message = msg
+    }
+
+    def cfg = new Configuration()
+    // Load the XML template
+    // TODO: Maybe there is a better way to define the templates path
+    def wtl = new WebappTemplateLoader(getServletContext(), "/WEB-INF/freemarker")
+    cfg.setTemplateLoader(wtl)
+    def ftl = cfg.getTemplate("respond-with-conference.ftl")
+    def xmlText = new StringWriter()
+    ftl.process([returnCode: RESP_CODE_SUCCESS, messageKey: messageKey,
+                 message: message, createdOn: createdOn, meeting: meeting], xmlText)
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(text: xmlText.toString(), contentType: "text/xml")
+      }
+    }
+  }
+
+  def respondWithErrors(errorList) {
+    log.debug CONTROLLER_NAME + "#invalid"
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          response1() {
+            returncode(RESP_CODE_FAILED)
+            errors() {
+              ArrayList errs = errorList.getErrors();
+              Iterator itr = errs.iterator();
+              while (itr.hasNext()){
+                String[] er = (String[]) itr.next();
+                log.debug CONTROLLER_NAME + "#invalid" + er[0]
+                error(key: er[0], message: er[1])
+              }
+            }
+          }
+        }
+      }
+      json {
+        log.debug "Rendering as json"
+        render(contentType:"text/json") {
+          returncode(RESP_CODE_FAILED)
+          messageKey(key)
+          message(msg)
+        }
+      }
+    }
+  }
+  //TODO: method added for backward compability, it will be removed in next versions after 0.8
+  def invalid(key, msg) {
+    String deprecatedMsg=" Note: This xml scheme will be DEPRECATED."
+    log.debug CONTROLLER_NAME + "#invalid"
+    InvalidResponse invalidResponse = new InvalidResponse(RESP_CODE_FAILED, key, msg);
+
+    def cfg = new Configuration()
+    // Load the XML template
+    // TODO: Maybe there is a better way to define the templates path
+    def wtl = new WebappTemplateLoader(getServletContext(), "/WEB-INF/freemarker")
+    cfg.setTemplateLoader(wtl)
+    def ftl = cfg.getTemplate("invalid-response.ftl")
+    def xmlText = new StringWriter()
+    ftl.process([returnCode: invalidResponse.returnCode, messageKey: invalidResponse.messageKey,
+                 message: invalidResponse.message], xmlText)
+
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      xml {
+        render(text: xmlText.toString(), contentType: "text/xml")
+      }
+      json {
+        log.debug "Rendering as json"
+        render(contentType:"text/json") {
+          returncode(RESP_CODE_FAILED)
+          messageKey(key)
+          message(msg)
+        }
+      }
+    }
+  }
+
+  def parseBoolean(obj) {
+    if (obj instanceof Number) {
+      return ((Number) obj).intValue() == 1;
+    }
+    return false
+  }
+
+}
diff --git a/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy
new file mode 100755
index 0000000000..89e8682005
--- /dev/null
+++ b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy
@@ -0,0 +1,296 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.web.controllers
+
+import grails.converters.*
+import org.bigbluebutton.web.services.PresentationService
+import org.bigbluebutton.presentation.UploadedPresentation
+import org.bigbluebutton.api.MeetingService;
+import org.bigbluebutton.api.Util;
+
+class PresentationController {
+  MeetingService meetingService
+  PresentationService presentationService
+  
+  def index = {
+    render(view:'upload-file') 
+  }
+  
+  def upload = {
+    def meetingId = params.conference
+    def meeting = meetingService.getNotEndedMeetingWithId(meetingId);
+    if (meeting == null) {
+      flash.message = 'meeting is not running'
+
+      response.addHeader("Cache-Control", "no-cache")
+      response.contentType = 'plain/text'
+      response.outputStream << 'no-meeting';
+      return null;
+    }
+
+    def file = request.getFile('fileUpload')
+    if (file && !file.empty) {
+      flash.message = 'Your file has been uploaded'
+      def presFilename = file.getOriginalFilename()
+      def filenameExt = Util.getFilenameExt(presFilename);
+      String presentationDir = presentationService.getPresentationDir()
+      def presId = Util.generatePresentationId(presFilename)
+      File uploadDir = Util.createPresentationDirectory(meetingId, presentationDir, presId) 
+      
+      if (uploadDir != null) {
+         def newFilename = Util.createNewFilename(presId, filenameExt)
+         def pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename )
+         file.transferTo(pres)
+         
+         def presentationBaseUrl = presentationService.presentationBaseUrl
+         UploadedPresentation uploadedPres = new UploadedPresentation(meetingId, presId, presFilename, presentationBaseUrl);
+         uploadedPres.setUploadedFile(pres);
+         presentationService.processUploadedPresentation(uploadedPres)
+      }
+    } else {
+      flash.message = 'file cannot be empty'
+    }
+
+      response.addHeader("Cache-Control", "no-cache")
+      response.contentType = 'plain/text'
+      response.outputStream << 'file-empty';
+    }
+
+  def testConversion = {
+    presentationService.testConversionProcess();
+  }
+
+  //handle external presentation server 
+  def delegate = {		
+    
+    def presentation_name = request.getParameter('presentation_name')
+    def conference = request.getParameter('conference')
+    def room = request.getParameter('room')
+    def returnCode = request.getParameter('returnCode')
+    def totalSlides = request.getParameter('totalSlides')
+    def slidesCompleted = request.getParameter('slidesCompleted')
+    
+     presentationService.processDelegatedPresentation(conference, room, presentation_name, returnCode, totalSlides, slidesCompleted)
+    redirect( action:list)
+  }
+  
+  def showSlide = {
+    def presentationName = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def slide = params.id
+    
+    InputStream is = null;
+    try {
+      def pres = presentationService.showSlide(conf, rm, presentationName, slide)
+      if (pres.exists()) {
+        def bytes = pres.readBytes()
+        response.addHeader("Cache-Control", "no-cache")
+        response.contentType = 'application/x-shockwave-flash'
+        response.outputStream << bytes;
+      }	
+    } catch (IOException e) {
+      log.error("Error reading file.\n" + e.getMessage());
+    }
+    
+    return null;
+  }
+  
+  def showSvgImage = {
+    def presentationName = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def slide = params.id
+  
+    InputStream is = null;
+    try {
+      def pres = presentationService.showSvgImage(conf, rm, presentationName, slide)
+      if (pres.exists()) {
+        def bytes = pres.readBytes()
+        response.addHeader("Cache-Control", "no-cache")
+        response.contentType = 'image/svg+xml'
+        response.outputStream << bytes;
+      }
+    } catch (IOException e) {
+      log.error("Error reading file.\n" + e.getMessage());
+    }
+  
+    return null;
+  }
+  
+  def showThumbnail = {
+    def presentationName = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def thumb = params.id
+    
+    InputStream is = null;
+    try {
+      def pres = presentationService.showThumbnail(conf, rm, presentationName, thumb)
+      if (pres.exists()) {
+        
+        def bytes = pres.readBytes()
+        response.addHeader("Cache-Control", "no-cache")
+        response.contentType = 'image'
+        response.outputStream << bytes;
+      }
+    } catch (IOException e) {
+      log.error("Error reading file.\n" + e.getMessage());
+    }
+    
+    return null;
+  }
+  
+  def showTextfile = {
+    def presentationName = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def textfile = params.id
+    log.debug "Controller: Show textfile request for $presentationName $textfile"
+    
+    InputStream is = null;
+    try {
+      def pres = presentationService.showTextfile(conf, rm, presentationName, textfile)
+      if (pres.exists()) {
+        log.debug "Controller: Sending textfiles reply for $presentationName $textfile"
+  
+        def bytes = pres.readBytes()
+        response.addHeader("Cache-Control", "no-cache")
+        response.contentType = 'plain/text'
+        response.outputStream << bytes;
+      } else {
+        log.debug "$pres does not exist."
+      }
+    } catch (IOException e) {
+      log.error("Error reading file.\n" + e.getMessage());
+    }
+  
+    return null;
+  }
+  
+  def thumbnail = {
+    def filename = params.id.replace('###', '.')
+    def presDir = confDir() + File.separatorChar + filename
+    try {
+      def pres = presentationService.showThumbnail(presDir, params.thumb)
+      if (pres.exists()) {
+        def bytes = pres.readBytes()
+
+        response.contentType = 'image'
+        response.outputStream << bytes;
+      }	
+    } catch (IOException e) {
+      log.error("Error reading file.\n" + e.getMessage());
+    }
+    
+    return null;
+  }
+
+  def numberOfSlides = {
+    def presentationName = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    
+    def numThumbs = presentationService.numberOfThumbnails(conf, rm, presentationName)
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {						
+        xml {
+          render(contentType:"text/xml") {
+            conference(id:conf, room:rm) {
+              presentation(name:presentationName) {
+                slides(count:numThumbs) {
+                  for (def i = 1; i <= numThumbs; i++) {
+                    slide(number:"${i}", name:"slide/${i}", thumb:"thumbnail/${i}", textfile:"textfile/${i}")
+                  }
+                }
+              }
+            }
+          }
+        }
+      }		
+  }
+    
+  def numberOfThumbnails = {
+    def filename = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def numThumbs = presentationService.numberOfThumbnails(conf, rm, filename)
+      withFormat {				
+        xml {
+          render(contentType:"text/xml") {
+            conference(id:f.conference, room:f.room) {
+              presentation(name:filename) {
+                thumbnails(count:numThumbs) {
+                  for (def i=0;i<numThumbs;i++) {
+                      thumb(name:"thumbnails/${i}")
+                    }
+                }
+              }
+            }
+          }
+        }
+      }		
+  }
+
+  def numberOfSvgs = {
+    def filename = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def numSvgs = presentationService.numberOfSvgs(conf, rm, filename)
+      withFormat {
+        xml {
+          render(contentType:"text/xml") {
+            conference(id:f.conference, room:f.room) {
+              presentation(name:filename) {
+                svgs(count:numSvgs) {
+                  for (def i=0;i<numSvgs;i++) {
+                      svg(name:"svgs/${i}")
+                    }
+                }
+              }
+            }
+          }
+        }
+      }
+  }
+
+  def numberOfTextfiles = {
+    def filename = params.presentation_name
+    def conf = params.conference
+    def rm = params.room
+    def numFiles = presentationService.numberOfTextfiles(conf, rm, filename)
+    
+    withFormat {
+      xml {
+        render(contentType:"text/xml") {
+          conference(id:f.conference, room:f.room) {
+            presentation(name:filename) {
+              textfiles(count:numFiles) {
+                for (def i=0;i<numFiles;i++) {
+                  textfile(name:"textfiles/${i}")
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
diff --git a/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/UrlMappings.groovy b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/UrlMappings.groovy
new file mode 100755
index 0000000000..4deb85df88
--- /dev/null
+++ b/bbb-web-api/grails-app/controllers/org/bigbluebutton/web/controllers/UrlMappings.groovy
@@ -0,0 +1,80 @@
+package org.bigbluebutton.web
+
+class UrlMappings {
+
+    static mappings = {
+        "/presentation/upload"(controller:"presentation") {
+            action = [POST:'upload']
+        }
+
+        "/presentation/test-convert"(controller:"presentation") {
+            action = [GET:'testConversion']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/slides"(controller:"presentation") {
+            action = [GET:'numberOfSlides']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/slide/$id"(controller:"presentation") {
+            action = [GET:'showSlide']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/thumbnails"(controller:"presentation") {
+            action = [GET:'numberOfThumbnails']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/thumbnail/$id"(controller:"presentation") {
+            action = [GET:'showThumbnail']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/svgs"(controller:"presentation") {
+            action = [GET:'numberOfSvgs']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/svg/$id"(controller:"presentation") {
+            action = [GET:'showSvgImage']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/textfiles"(controller:"presentation") {
+            action = [GET:'numberOfTextfiles']
+        }
+
+        "/presentation/$conference/$room/$presentation_name/textfiles/$id"(controller:"presentation") {
+            action = [GET:'showTextfile']
+        }
+
+        "/api/setConfigXML"(controller:"api") {
+            action = [POST:'setConfigXML']
+        }
+
+        "/api/setPollXML"(controller:"api") {
+            action = [POST:'setPollXML']
+        }
+
+        "/api/getMeetings"(controller:"api") {
+            action = [GET:'getMeetingsHandler', POST:'getMeetingsHandler']
+        }
+
+
+        "/api/getSessions"(controller:"api") {
+            action = [GET:'getSessionsHandler', POST:'getSessionsHandler']
+        }
+
+        "/api/getRecordings"(controller:"api") {
+            action = [GET:'getRecordingsHandler', POST:'getRecordingsHandler']
+        }
+
+        "/$controller/$action?/$id?(.$format)?"{
+            constraints {
+                // apply constraints here
+            }
+        }
+
+        "/"(controller:"api") {
+            action = [GET:'index']
+        }
+        "500"(view:'/error')
+        "404"(view:'/notFound')
+    }
+}
+
diff --git a/bbb-web-api/grails-app/i18n/messages.properties b/bbb-web-api/grails-app/i18n/messages.properties
new file mode 100755
index 0000000000..b045136211
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages.properties
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
+default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
+default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
+default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
+default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
+default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
+default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
+default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
+default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
+default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
+default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
+default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
+default.blank.message=Property [{0}] of class [{1}] cannot be blank
+default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
+default.null.message=Property [{0}] of class [{1}] cannot be null
+default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
+
+default.paginate.prev=Previous
+default.paginate.next=Next
+default.boolean.true=True
+default.boolean.false=False
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} created
+default.updated.message={0} {1} updated
+default.deleted.message={0} {1} deleted
+default.not.deleted.message={0} {1} could not be deleted
+default.not.found.message={0} not found with id {1}
+default.optimistic.locking.failure=Another user has updated this {0} while you were editing
+
+default.home.label=Home
+default.list.label={0} List
+default.add.label=Add {0}
+default.new.label=New {0}
+default.create.label=Create {0}
+default.show.label=Show {0}
+default.edit.label=Edit {0}
+
+default.button.create.label=Create
+default.button.edit.label=Edit
+default.button.update.label=Update
+default.button.delete.label=Delete
+default.button.delete.confirm.message=Are you sure?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Property {0} must be a valid URL
+typeMismatch.java.net.URI=Property {0} must be a valid URI
+typeMismatch.java.util.Date=Property {0} must be a valid Date
+typeMismatch.java.lang.Double=Property {0} must be a valid number
+typeMismatch.java.lang.Integer=Property {0} must be a valid number
+typeMismatch.java.lang.Long=Property {0} must be a valid number
+typeMismatch.java.lang.Short=Property {0} must be a valid number
+typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
+typeMismatch.java.math.BigInteger=Property {0} must be a valid number
+typeMismatch=Property {0} is type-mismatched
diff --git a/bbb-web-api/grails-app/i18n/messages_cs_CZ.properties b/bbb-web-api/grails-app/i18n/messages_cs_CZ.properties
new file mode 100755
index 0000000000..73455311c8
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_cs_CZ.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neodpovídá požadovanému vzoru [{3}]
+default.invalid.url.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní URL
+default.invalid.creditCard.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní číslo kreditní karty
+default.invalid.email.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní emailová adresa
+default.invalid.range.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
+default.invalid.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
+default.invalid.max.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální povolenou hodnotu [{3}]
+default.invalid.min.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální povolená hodnota [{3}]
+default.invalid.max.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální velikost [{3}]
+default.invalid.min.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální velikost [{3}]
+default.invalid.validator.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neprošla validací
+default.not.inlist.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není obsažena v seznamu [{3}]
+default.blank.message=Položka [{0}] třídy [{1}] nemůže být prázdná
+default.not.equal.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] nemůže být stejná jako [{3}]
+default.null.message=Položka [{0}] třídy [{1}] nemůže být prázdná
+default.not.unique.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] musí být unikátní
+
+default.paginate.prev=Předcházející
+default.paginate.next=Následující
+default.boolean.true=Pravda
+default.boolean.false=Nepravda
+default.date.format=dd. MM. yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} vytvořeno
+default.updated.message={0} {1} aktualizováno
+default.deleted.message={0} {1} smazáno
+default.not.deleted.message={0} {1} nelze smazat
+default.not.found.message={0} nenalezen s id {1}
+default.optimistic.locking.failure=Jiný uživatel aktualizoval záznam {0}, právě když byl vámi editován
+
+default.home.label=Domů
+default.list.label={0} Seznam
+default.add.label=Přidat {0}
+default.new.label=Nový {0}
+default.create.label=Vytvořit {0}
+default.show.label=Ukázat {0}
+default.edit.label=Editovat {0}
+
+default.button.create.label=VytvoÅ™
+default.button.edit.label=Edituj
+default.button.update.label=Aktualizuj
+default.button.delete.label=Smaž
+default.button.delete.confirm.message=Jste si jistý?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Položka {0} musí být validní URL
+typeMismatch.java.net.URI=Položka {0} musí být validní URI
+typeMismatch.java.util.Date=Položka {0} musí být validní datum
+typeMismatch.java.lang.Double=Položka {0} musí být validní desetinné číslo
+typeMismatch.java.lang.Integer=Položka {0} musí být validní číslo
+typeMismatch.java.lang.Long=Položka {0} musí být validní číslo
+typeMismatch.java.lang.Short=Položka {0} musí být validní číslo
+typeMismatch.java.math.BigDecimal=Položka {0} musí být validní číslo
+typeMismatch.java.math.BigInteger=Položka {0} musí být validní číslo
diff --git a/bbb-web-api/grails-app/i18n/messages_da.properties b/bbb-web-api/grails-app/i18n/messages_da.properties
new file mode 100755
index 0000000000..858b22903c
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_da.properties
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke mønsteret [{3}]
+default.invalid.url.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig URL
+default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke et gyldigt kreditkortnummer
+default.invalid.email.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig e-mail adresse
+default.invalid.range.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for intervallet fra  [{3}] til [{4}]
+default.invalid.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for størrelsen fra [{3}] til [{4}]
+default.invalid.max.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale værdi [{3}]
+default.invalid.min.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale værdi [{3}]
+default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale størrelse på [{3}]
+default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale størrelse på [{3}]
+default.invalid.validator.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke den brugerdefinerede validering
+default.not.inlist.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] findes ikke i listen [{3}]
+default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
+default.not.equal.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] må ikke være [{3}]
+default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
+default.not.unique.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] skal være unik
+
+default.paginate.prev=Forrige
+default.paginate.next=Næste
+default.boolean.true=Sand
+default.boolean.false=Falsk
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} oprettet
+default.updated.message={0} {1} opdateret
+default.deleted.message={0} {1} slettet
+default.not.deleted.message={0} {1} kunne ikke slettes
+default.not.found.message={0} med id {1} er ikke fundet
+default.optimistic.locking.failure=En anden bruger har opdateret denne {0} imens du har lavet rettelser
+
+default.home.label=Hjem
+default.list.label={0} Liste
+default.add.label=Tilføj {0}
+default.new.label=Ny {0}
+default.create.label=Opret {0}
+default.show.label=Vis {0}
+default.edit.label=Ret {0}
+
+default.button.create.label=Opret
+default.button.edit.label=Ret
+default.button.update.label=Opdater
+default.button.delete.label=Slet
+default.button.delete.confirm.message=Er du sikker?
+
+# Databindingsfejl. Brug "typeMismatch.$className.$propertyName for at passe til en given klasse (f.eks typeMismatch.Book.author)
+typeMismatch.java.net.URL=Feltet {0} skal være en valid URL
+typeMismatch.java.net.URI=Feltet {0} skal være en valid URI
+typeMismatch.java.util.Date=Feltet {0} skal være en valid Dato
+typeMismatch.java.lang.Double=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Integer=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Long=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Short=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigDecimal=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigInteger=Feltet {0} skal være et valid tal
+
diff --git a/bbb-web-api/grails-app/i18n/messages_de.properties b/bbb-web-api/grails-app/i18n/messages_de.properties
new file mode 100755
index 0000000000..0f7bfe92c3
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_de.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}]
+default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige URL
+default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige Kreditkartennummer
+default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige E-Mail Adresse
+default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist größer als der Höchstwert von [{3}]
+default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}]
+default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] übersteigt den Höchstwert von [{3}]
+default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}]
+default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungültig
+default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten.
+default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein
+default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein
+default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein
+default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen
+
+default.paginate.prev=Vorherige
+default.paginate.next=Nächste
+default.boolean.true=Wahr
+default.boolean.false=Falsch
+default.date.format=dd.MM.yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} wurde angelegt
+default.updated.message={0} {1} wurde geändert
+default.deleted.message={0} {1} wurde gelöscht
+default.not.deleted.message={0} {1} konnte nicht gelöscht werden
+default.not.found.message={0} mit der id {1} wurde nicht gefunden
+default.optimistic.locking.failure=Ein anderer Benutzer hat das {0} Object geändert während Sie es bearbeitet haben
+
+default.home.label=Home
+default.list.label={0} Liste
+default.add.label={0} hinzufügen
+default.new.label={0} anlegen
+default.create.label={0} anlegen
+default.show.label={0} anzeigen
+default.edit.label={0} bearbeiten
+
+default.button.create.label=Anlegen
+default.button.edit.label=Bearbeiten
+default.button.update.label=Aktualisieren
+default.button.delete.label=Löschen
+default.button.delete.confirm.message=Sind Sie sicher?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gültige URL sein
+typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gültige URI sein
+typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gültiges Datum sein
+typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gültige Zahl sein
diff --git a/bbb-web-api/grails-app/i18n/messages_es.properties b/bbb-web-api/grails-app/i18n/messages_es.properties
new file mode 100755
index 0000000000..f0ede53968
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_es.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrón [{3}]
+default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL válida
+default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un número de tarjeta de crédito válida
+default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una dirección de correo electrónico válida
+default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango válido de [{3}] a [{4}]
+default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaño válido de [{3}] a [{4}]
+default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor máximo [{3}]
+default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mínimo [{3}]
+default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaño máximo de [{3}]
+default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaño mínimo de [{3}]
+default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es válido
+default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}]
+default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacía
+default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}]
+default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo
+default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser única
+
+default.paginate.prev=Anterior
+default.paginate.next=Siguiente
+default.boolean.true=Verdadero
+default.boolean.false=Falso
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} creado
+default.updated.message={0} {1} actualizado
+default.deleted.message={0} {1} eliminado
+default.not.deleted.message={0} {1} no puede eliminarse
+default.not.found.message=No se encuentra {0} con id {1}
+default.optimistic.locking.failure=Mientras usted editaba, otro usuario ha actualizado su {0}
+
+default.home.label=Principal
+default.list.label={0} Lista
+default.add.label=Agregar {0}
+default.new.label=Nuevo {0}
+default.create.label=Crear {0}
+default.show.label=Mostrar {0}
+default.edit.label=Editar {0}
+
+default.button.create.label=Crear
+default.button.edit.label=Editar
+default.button.update.label=Actualizar
+default.button.delete.label=Eliminar
+default.button.delete.confirm.message=¿Está usted seguro?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=La propiedad {0} debe ser una URL válida
+typeMismatch.java.net.URI=La propiedad {0} debe ser una URI válida
+typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha válida
+typeMismatch.java.lang.Double=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Integer=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Long=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Short=La propiedad {0} debe ser un número válido
+typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un número válido
+typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un número válido
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/i18n/messages_fr.properties b/bbb-web-api/grails-app/i18n/messages_fr.properties
new file mode 100755
index 0000000000..5572164149
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_fr.properties
@@ -0,0 +1,19 @@
+default.doesnt.match.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}]
+default.invalid.url.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide
+default.invalid.creditCard.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numéro de carte de crédit valide
+default.invalid.email.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide
+default.invalid.range.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
+default.invalid.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
+default.invalid.max.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
+default.invalid.min.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
+default.invalid.max.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
+default.invalid.min.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
+default.invalid.validator.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide
+default.not.inlist.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}]
+default.blank.message=La propriété [{0}] de la classe [{1}] ne peut pas être vide
+default.not.equal.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas être égale à [{3}]
+default.null.message=La propriété [{0}] de la classe [{1}] ne peut pas être nulle
+default.not.unique.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] doit être unique
+
+default.paginate.prev=Précédent
+default.paginate.next=Suivant
diff --git a/bbb-web-api/grails-app/i18n/messages_it.properties b/bbb-web-api/grails-app/i18n/messages_it.properties
new file mode 100755
index 0000000000..a90f1c72c3
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_it.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}]
+default.invalid.url.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un URL valido
+default.invalid.creditCard.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un numero di carta di credito valido
+default.invalid.email.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un indirizzo email valido
+default.invalid.range.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}]
+default.invalid.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}]
+default.invalid.max.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
+default.invalid.min.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
+default.invalid.max.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
+default.invalid.min.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
+default.invalid.validator.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è valida
+default.not.inlist.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è contenuta nella lista [{3}]
+default.blank.message=La proprietà [{0}] della classe [{1}] non può essere vuota
+default.not.equal.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non può essere uguale a [{3}]
+default.null.message=La proprietà [{0}] della classe [{1}] non può essere null
+default.not.unique.message=La proprietà [{0}] della classe [{1}] con valore [{2}] deve essere unica
+
+default.paginate.prev=Precedente
+default.paginate.next=Successivo
+default.boolean.true=Vero
+default.boolean.false=Falso
+default.date.format=dd/MM/yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} creato
+default.updated.message={0} {1} aggiornato
+default.deleted.message={0} {1} eliminato
+default.not.deleted.message={0} {1} non può essere eliminato
+default.not.found.message={0} non trovato con id {1}
+default.optimistic.locking.failure=Un altro utente ha aggiornato questo {0} mentre si era in modifica
+
+default.home.label=Home
+default.list.label={0} Elenco
+default.add.label=Aggiungi {0}
+default.new.label=Nuovo {0}
+default.create.label=Crea {0}
+default.show.label=Mostra {0}
+default.edit.label=Modifica {0}
+
+default.button.create.label=Crea
+default.button.edit.label=Modifica
+default.button.update.label=Aggiorna
+default.button.delete.label=Elimina
+default.button.delete.confirm.message=Si è sicuri?
+
+# Data binding errors. Usa "typeMismatch.$className.$propertyName per la personalizzazione (es typeMismatch.Book.author)
+typeMismatch.java.net.URL=La proprietà {0} deve essere un URL valido
+typeMismatch.java.net.URI=La proprietà {0} deve essere un URI valido
+typeMismatch.java.util.Date=La proprietà {0} deve essere una data valida
+typeMismatch.java.lang.Double=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Integer=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Long=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Short=La proprietà {0} deve essere un numero valido
+typeMismatch.java.math.BigDecimal=La proprietà {0} deve essere un numero valido
+typeMismatch.java.math.BigInteger=La proprietà {0} deve essere un numero valido
diff --git a/bbb-web-api/grails-app/i18n/messages_ja.properties b/bbb-web-api/grails-app/i18n/messages_ja.properties
new file mode 100755
index 0000000000..f716d75be2
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_ja.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。
+default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なURLではありません。
+default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なクレジットカード番号ではありません。
+default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なメールアドレスではありません。
+default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。
+default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。
+default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
+default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
+default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
+default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
+default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。
+default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。
+default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。
+default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]と同等ではありません。
+default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。
+default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。
+
+default.paginate.prev=戻る
+default.paginate.next=次へ
+default.boolean.true=はい
+default.boolean.false=いいえ
+default.date.format=yyyy/MM/dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0}(id:{1})を作成しました。
+default.updated.message={0}(id:{1})を更新しました。
+default.deleted.message={0}(id:{1})を削除しました。
+default.not.deleted.message={0}(id:{1})は削除できませんでした。
+default.not.found.message={0}(id:{1})は見つかりませんでした。
+default.optimistic.locking.failure=この{0}は編集中に他のユーザによって先に更新されています。
+
+default.home.label=ホーム
+default.list.label={0}リスト
+default.add.label={0}を追加
+default.new.label={0}を新規作成
+default.create.label={0}を作成
+default.show.label={0}詳細
+default.edit.label={0}を編集
+
+default.button.create.label=作成
+default.button.edit.label=編集
+default.button.update.label=æ›´æ–°
+default.button.delete.label=削除
+default.button.delete.confirm.message=本当に削除してよろしいですか?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL={0}は有効なURLでなければなりません。
+typeMismatch.java.net.URI={0}は有効なURIでなければなりません。
+typeMismatch.java.util.Date={0}は有効な日付でなければなりません。
+typeMismatch.java.lang.Double={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Integer={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Long={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Short={0}は有効な数値でなければなりません。
+typeMismatch.java.math.BigDecimal={0}は有効な数値でなければなりません。
+typeMismatch.java.math.BigInteger={0}は有効な数値でなければなりません。
diff --git a/bbb-web-api/grails-app/i18n/messages_nb.properties b/bbb-web-api/grails-app/i18n/messages_nb.properties
new file mode 100755
index 0000000000..47a8a1abe7
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_nb.properties
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke mønsteret [{3}]
+default.invalid.url.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig URL
+default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke et gyldig kredittkortnummer
+default.invalid.email.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig epostadresse
+default.invalid.range.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}]
+default.invalid.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}]
+default.invalid.max.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumsverdien på [{3}]
+default.invalid.min.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er under minimumsverdien på [{3}]
+default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumslengden på [{3}]
+default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er kortere enn minimumslengden på [{3}]
+default.invalid.validator.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke den brukerdefinerte valideringen
+default.not.inlist.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] finnes ikke i listen [{3}]
+default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
+default.not.equal.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] kan ikke være [{3}]
+default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
+default.not.unique.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] må være unik
+
+default.paginate.prev=Forrige
+default.paginate.next=Neste
+default.boolean.true=Ja
+default.boolean.false=Nei
+default.date.format=dd.MM.yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} opprettet
+default.updated.message={0} {1} oppdatert
+default.deleted.message={0} {1} slettet
+default.not.deleted.message={0} {1} kunne ikke slettes
+default.not.found.message={0} med id {1} ble ikke funnet
+default.optimistic.locking.failure=En annen bruker har oppdatert denne {0} mens du redigerte
+
+default.home.label=Hjem
+default.list.label={0}liste
+default.add.label=Legg til {0}
+default.new.label=Ny {0}
+default.create.label=Opprett {0}
+default.show.label=Vis {0}
+default.edit.label=Endre {0}
+
+default.button.create.label=Opprett
+default.button.edit.label=Endre
+default.button.update.label=Oppdater
+default.button.delete.label=Slett
+default.button.delete.confirm.message=Er du sikker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Feltet {0} må være en gyldig URL
+typeMismatch.java.net.URI=Feltet {0} må være en gyldig URI
+typeMismatch.java.util.Date=Feltet {0} må være en gyldig dato
+typeMismatch.java.lang.Double=Feltet {0} må være et gyldig tall
+typeMismatch.java.lang.Integer=Feltet {0} må være et gyldig heltall
+typeMismatch.java.lang.Long=Feltet {0} må være et gyldig heltall
+typeMismatch.java.lang.Short=Feltet {0} må være et gyldig heltall
+typeMismatch.java.math.BigDecimal=Feltet {0} må være et gyldig tall
+typeMismatch.java.math.BigInteger=Feltet {0} må være et gyldig heltall
+
diff --git a/bbb-web-api/grails-app/i18n/messages_nl.properties b/bbb-web-api/grails-app/i18n/messages_nl.properties
new file mode 100755
index 0000000000..cd5cc94ee7
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_nl.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet overeen met het vereiste patroon [{3}]
+default.invalid.url.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldige URL
+default.invalid.creditCard.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig credit card nummer
+default.invalid.email.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig e-mailadres
+default.invalid.range.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige waardenreeks van [{3}] tot [{4}]
+default.invalid.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige grootte van [{3}] tot [{4}]
+default.invalid.max.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumwaarde [{3}]
+default.invalid.min.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan de minimumwaarde [{3}]
+default.invalid.max.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumgrootte van [{3}]
+default.invalid.min.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan minimumgrootte van [{3}]
+default.invalid.validator.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is niet geldig
+default.not.inlist.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet voor in de lijst [{3}]
+default.blank.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.equal.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] mag niet gelijk zijn aan [{3}]
+default.null.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.unique.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] moet uniek zijn
+
+default.paginate.prev=Vorige
+default.paginate.next=Volgende
+default.boolean.true=Ja
+default.boolean.false=Nee
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} ingevoerd
+default.updated.message={0} {1} gewijzigd
+default.deleted.message={0} {1} verwijderd
+default.not.deleted.message={0} {1} kon niet worden verwijderd
+default.not.found.message={0} met id {1} kon niet worden gevonden
+default.optimistic.locking.failure=Een andere gebruiker heeft deze {0} al gewijzigd
+
+default.home.label=Home
+default.list.label={0} Overzicht
+default.add.label=Toevoegen {0}
+default.new.label=Invoeren {0}
+default.create.label=Invoeren {0}
+default.show.label=Details {0}
+default.edit.label=Wijzigen {0}
+
+default.button.create.label=Invoeren
+default.button.edit.label=Wijzigen
+default.button.update.label=Opslaan
+default.button.delete.label=Verwijderen
+default.button.delete.confirm.message=Weet je het zeker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Attribuut {0} is geen geldige URL
+typeMismatch.java.net.URI=Attribuut {0} is geen geldige URI
+typeMismatch.java.util.Date=Attribuut {0} is geen geldige datum
+typeMismatch.java.lang.Double=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Integer=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Long=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Short=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigDecimal=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigInteger=Attribuut {0} is geen geldig nummer
diff --git a/bbb-web-api/grails-app/i18n/messages_pl.properties b/bbb-web-api/grails-app/i18n/messages_pl.properties
new file mode 100755
index 0000000000..959296cee3
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_pl.properties
@@ -0,0 +1,59 @@
+#
+# Translated by Matthias Hryniszak - padcom@gmail.com
+#
+
+default.doesnt.match.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie pasuje do wymaganego wzorca [{3}]
+default.invalid.url.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest niepoprawnym adresem URL
+default.invalid.creditCard.message=Właściwość [{0}] klasy [{1}] with value [{2}] nie jest poprawnym numerem karty kredytowej
+default.invalid.email.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie jest poprawnym adresem e-mail
+default.invalid.range.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się zakładanym zakresie od [{3}] do [{4}]
+default.invalid.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w zakładanym zakresie rozmiarów od [{3}] do [{4}]
+default.invalid.max.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalną wartość [{3}]
+default.invalid.min.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalna wartość [{3}]
+default.invalid.max.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalny rozmiar [{3}]
+default.invalid.min.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalny rozmiar [{3}]
+default.invalid.validator.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie spełnia założonych niestandardowych warunków
+default.not.inlist.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w liście [{3}]
+default.blank.message=Właściwość [{0}] klasy [{1}] nie może być pusta
+default.not.equal.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie może równać się [{3}]
+default.null.message=Właściwość [{0}] klasy [{1}] nie może być null
+default.not.unique.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] musi być unikalna
+
+default.paginate.prev=Poprzedni
+default.paginate.next=Następny
+default.boolean.true=Prawda
+default.boolean.false=Fałsz
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message=Utworzono {0} {1}
+default.updated.message=Zaktualizowano {0} {1}
+default.deleted.message=Usunięto {0} {1}
+default.not.deleted.message={0} {1} nie mógł zostać usunięty
+default.not.found.message=Nie znaleziono {0} o id {1}
+default.optimistic.locking.failure=Inny użytkownik zaktualizował ten obiekt {0} w trakcie twoich zmian
+
+default.home.label=Strona domowa
+default.list.label=Lista {0}
+default.add.label=Dodaj {0}
+default.new.label=Utwórz {0}
+default.create.label=Utwórz {0}
+default.show.label=Pokaż {0}
+default.edit.label=Edytuj {0}
+
+default.button.create.label=Utwórz
+default.button.edit.label=Edytuj
+default.button.update.label=Zaktualizuj
+default.button.delete.label=Usuń
+default.button.delete.confirm.message=Czy jesteÅ› pewien?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Właściwość {0} musi być poprawnym adresem URL
+typeMismatch.java.net.URI=Właściwość {0} musi być poprawnym adresem URI
+typeMismatch.java.util.Date=Właściwość {0} musi być poprawną datą
+typeMismatch.java.lang.Double=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Integer=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Long=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Short=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.math.BigDecimal=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.math.BigInteger=Właściwość {0} musi być poprawnyą liczbą
diff --git a/bbb-web-api/grails-app/i18n/messages_pt_BR.properties b/bbb-web-api/grails-app/i18n/messages_pt_BR.properties
new file mode 100755
index 0000000000..b5044e23e0
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_pt_BR.properties
@@ -0,0 +1,59 @@
+#
+# Translated by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atende ao padrão definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é uma URL válida
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está entre a faixa de valores válida de [{3}] até [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está na faixa de tamanho válida de [{3}] até [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um valor dentre os permitidos na lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] não pode ficar em branco
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único
+
+default.paginate.prev=Anterior
+default.paginate.next=Próximo
+default.boolean.true=Sim
+default.boolean.false=Não
+default.date.format=dd/MM/yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} criado
+default.updated.message={0} {1} atualizado
+default.deleted.message={0} {1} removido
+default.not.deleted.message={0} {1} não pode ser removido
+default.not.found.message={0} não foi encontrado com o id {1}
+default.optimistic.locking.failure=Outro usuário atualizou este [{0}] enquanto você tentou salvá-lo
+
+default.home.label=Principal
+default.list.label={0} Listagem
+default.add.label=Adicionar {0}
+default.new.label=Novo {0}
+default.create.label=Criar {0}
+default.show.label=Ver {0}
+default.edit.label=Editar {0}
+
+default.button.create.label=Criar
+default.button.edit.label=Editar
+default.button.update.label=Alterar
+default.button.delete.label=Remover
+default.button.delete.confirm.message=Tem certeza?
+
+# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para customizar (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser uma URL válida.
+typeMismatch.java.net.URI=O campo {0} deve ser uma URI válida.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data válida
+typeMismatch.java.lang.Double=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido.
diff --git a/bbb-web-api/grails-app/i18n/messages_pt_PT.properties b/bbb-web-api/grails-app/i18n/messages_pt_PT.properties
new file mode 100755
index 0000000000..a386070674
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_pt_PT.properties
@@ -0,0 +1,34 @@
+#
+# translation by miguel.ping@gmail.com, based on pt_BR translation by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não corresponde ao padrão definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um URL válido
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está dentro dos limites de valores válidos de [{3}] a [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] está fora dos limites de tamanho válido de [{3}] a [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não se encontra nos valores permitidos da lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único
+
+default.paginate.prev=Anterior
+default.paginate.next=Próximo
+
+# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para personalizar(eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser um URL válido.
+typeMismatch.java.net.URI=O campo {0} deve ser um URI válido.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data válida
+typeMismatch.java.lang.Double=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um número valido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido.
diff --git a/bbb-web-api/grails-app/i18n/messages_ru.properties b/bbb-web-api/grails-app/i18n/messages_ru.properties
new file mode 100755
index 0000000000..53a4bdc431
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_ru.properties
@@ -0,0 +1,31 @@
+default.doesnt.match.message=Значение [{2}] поля [{0}] класса [{1}] не соответствует образцу [{3}]
+default.invalid.url.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым URL-адресом
+default.invalid.creditCard.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым номером кредитной карты
+default.invalid.email.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым e-mail адресом
+default.invalid.range.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в допустимый интервал от [{3}] до [{4}]
+default.invalid.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) не попадает в допустимый интервал от [{3}] до [{4}]
+default.invalid.max.message=Значение [{2}] поля [{0}] класса [{1}] больше чем максимально допустимое значение [{3}]
+default.invalid.min.message=Значение [{2}] поля [{0}] класса [{1}] меньше чем минимально допустимое значение [{3}]
+default.invalid.max.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) больше чем максимально допустимый размер [{3}]
+default.invalid.min.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) меньше чем минимально допустимый размер [{3}]
+default.invalid.validator.message=Значение [{2}] поля [{0}] класса [{1}] не допустимо
+default.not.inlist.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в список допустимых значений [{3}]
+default.blank.message=Поле [{0}] класса [{1}] не может быть пустым
+default.not.equal.message=Значение [{2}] поля [{0}] класса [{1}] не может быть равно [{3}]
+default.null.message=Поле [{0}] класса [{1}] не может иметь значение null
+default.not.unique.message=Значение [{2}] поля [{0}] класса [{1}] должно быть уникальным
+
+default.paginate.prev=Предыдушая страница
+default.paginate.next=Следующая страница
+
+# Ошибки при присвоении данных. Для точной настройки для полей классов используйте
+# формат "typeMismatch.$className.$propertyName" (например, typeMismatch.Book.author)
+typeMismatch.java.net.URL=Значение поля {0} не является допустимым URL
+typeMismatch.java.net.URI=Значение поля {0} не является допустимым URI
+typeMismatch.java.util.Date=Значение поля {0} не является допустимой датой
+typeMismatch.java.lang.Double=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Integer=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Long=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Short=Значение поля {0} не является допустимым числом
+typeMismatch.java.math.BigDecimal=Значение поля {0} не является допустимым числом
+typeMismatch.java.math.BigInteger=Значение поля {0} не является допустимым числом
diff --git a/bbb-web-api/grails-app/i18n/messages_sv.properties b/bbb-web-api/grails-app/i18n/messages_sv.properties
new file mode 100755
index 0000000000..61899d7949
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_sv.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Attributet [{0}] för klassen [{1}] med värde [{2}] matchar inte mot uttrycket [{3}]
+default.invalid.url.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig URL
+default.invalid.creditCard.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte ett giltigt kreditkortsnummer
+default.invalid.email.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig e-postadress
+default.invalid.range.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte inom intervallet [{3}] till [{4}]
+default.invalid.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] har en storlek som inte är inom [{3}] till [{4}]
+default.invalid.max.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxvärdet [{3}]
+default.invalid.min.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimivärdet [{3}]
+default.invalid.max.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxstorleken [{3}]
+default.invalid.min.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimistorleken [{3}]
+default.invalid.validator.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt enligt anpassad regel
+default.not.inlist.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt, måste vara ett av [{3}]
+default.blank.message=Attributet [{0}] för klassen [{1}] får inte vara tomt
+default.not.equal.message=Attributet [{0}] för klassen [{1}] med värde [{2}] får inte vara lika med [{3}]
+default.null.message=Attributet [{0}] för klassen [{1}] får inte vara tomt
+default.not.unique.message=Attributet [{0}] för klassen [{1}] med värde [{2}] måste vara unikt
+
+default.paginate.prev=Föregående
+default.paginate.next=Nästa
+default.boolean.true=Sant
+default.boolean.false=Falskt
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} skapades
+default.updated.message={0} {1} uppdaterades
+default.deleted.message={0} {1} borttagen
+default.not.deleted.message={0} {1} kunde inte tas bort
+default.not.found.message={0} med id {1} kunde inte hittas
+default.optimistic.locking.failure=En annan användare har uppdaterat det här {0} objektet medan du redigerade det
+
+default.home.label=Hem
+default.list.label= {0} - Lista
+default.add.label=Lägg till {0}
+default.new.label=Skapa {0}
+default.create.label=Skapa {0}
+default.show.label=Visa {0}
+default.edit.label=Ändra {0}
+
+default.button.create.label=Skapa
+default.button.edit.label=Ändra
+default.button.update.label=Uppdatera
+default.button.delete.label=Ta bort
+default.button.delete.confirm.message=Är du säker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Värdet för {0} måste vara en giltig URL
+typeMismatch.java.net.URI=Värdet för {0} måste vara en giltig URI
+typeMismatch.java.util.Date=Värdet {0} måste vara ett giltigt datum
+typeMismatch.java.lang.Double=Värdet {0} måste vara ett giltigt nummer
+typeMismatch.java.lang.Integer=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.lang.Long=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.lang.Short=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.math.BigDecimal=Värdet {0} måste vara ett giltigt nummer
+typeMismatch.java.math.BigInteger=Värdet {0} måste vara ett giltigt heltal
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/i18n/messages_th.properties b/bbb-web-api/grails-app/i18n/messages_th.properties
new file mode 100755
index 0000000000..fd0dbb68d4
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_th.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบที่กำหนดไว้ใน [{3}]
+default.invalid.url.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบ URL
+default.invalid.creditCard.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบหมายเลขบัตรเครดิต
+default.invalid.email.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบอีเมล์
+default.invalid.range.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีค่าที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีขนาดที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.max.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าเกิดกว่าค่ามากสุด [{3}]
+default.invalid.min.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าน้อยกว่าค่าต่ำสุด  [{3}]
+default.invalid.max.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดเกินกว่าขนาดมากสุดของ [{3}]
+default.invalid.min.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดต่ำกว่าขนาดต่ำสุดของ  [{3}]
+default.invalid.validator.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ผ่านการทวนสอบค่าที่ตั้งขึ้น
+default.not.inlist.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้อยู่ในรายการต่อไปนี้  [{3}]
+default.blank.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็นค่าว่างได้
+default.not.equal.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่สามารถเท่ากับ [{3}] ได้
+default.null.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็น null ได้
+default.not.unique.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] จะต้องไม่ซ้ำ (unique)
+
+default.paginate.prev=ก่อนหน้า
+default.paginate.next=ถัดไป
+default.boolean.true=จริง
+default.boolean.false=เท็จ
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message=สร้าง {0} {1} เรียบร้อยแล้ว
+default.updated.message=ปรับปรุง {0} {1} เรียบร้อยแล้ว
+default.deleted.message=ลบ {0} {1} เรียบร้อยแล้ว
+default.not.deleted.message=ไม่สามารถลบ {0} {1}
+default.not.found.message=ไม่พบ {0} ด้วย id {1} นี้
+default.optimistic.locking.failure=มีผู้ใช้ท่านอื่นปรับปรุง {0} ขณะที่คุณกำลังแก้ไขข้อมูลอยู่
+
+default.home.label=หน้าแรก
+default.list.label=รายการ {0}
+default.add.label=เพิ่ม {0}
+default.new.label=สร้าง {0} ใหม่
+default.create.label=สร้าง {0}
+default.show.label=แสดง {0}
+default.edit.label=แก้ไข {0}
+
+default.button.create.label=สร้าง
+default.button.edit.label=แก้ไข
+default.button.update.label=ปรับปรุง
+default.button.delete.label=ลบ
+default.button.delete.confirm.message=คุณแน่ใจหรือไม่ ?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=คุณสมบัติ '{0}' จะต้องเป็นค่า URL ที่ถูกต้อง
+typeMismatch.java.net.URI=คุณสมบัติ '{0}' จะต้องเป็นค่า URI ที่ถูกต้อง
+typeMismatch.java.util.Date=คุณสมบัติ '{0}' จะต้องมีค่าเป็นวันที่
+typeMismatch.java.lang.Double=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Double
+typeMismatch.java.lang.Integer=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Integer
+typeMismatch.java.lang.Long=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Long
+typeMismatch.java.lang.Short=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Short
+typeMismatch.java.math.BigDecimal=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigDecimal
+typeMismatch.java.math.BigInteger=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigInteger
diff --git a/bbb-web-api/grails-app/i18n/messages_zh_CN.properties b/bbb-web-api/grails-app/i18n/messages_zh_CN.properties
new file mode 100755
index 0000000000..b89bc933bb
--- /dev/null
+++ b/bbb-web-api/grails-app/i18n/messages_zh_CN.properties
@@ -0,0 +1,18 @@
+default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A
+default.doesnt.match.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E\u5B9A\u4E49\u7684\u6A21\u5F0F [{3}]\u4E0D\u5339\u914D
+default.invalid.creditCard.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u4FE1\u7528\u5361\u53F7
+default.invalid.email.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
+default.invalid.max.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.max.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.min.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.min.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.range.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.url.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL
+default.invalid.validator.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u672A\u80FD\u901A\u8FC7\u81EA\u5B9A\u4E49\u7684\u9A8C\u8BC1
+default.not.equal.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E[{3}]\u4E0D\u76F8\u7B49
+default.not.inlist.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5217\u8868\u7684\u53D6\u503C\u8303\u56F4\u5185
+default.not.unique.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u5FC5\u987B\u662F\u552F\u4E00\u7684
+default.null.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3Anull
+default.paginate.next=\u4E0B\u9875
+default.paginate.prev=\u4E0A\u9875
diff --git a/bbb-web-api/grails-app/init/BootStrap.groovy b/bbb-web-api/grails-app/init/BootStrap.groovy
new file mode 100755
index 0000000000..1287daee1d
--- /dev/null
+++ b/bbb-web-api/grails-app/init/BootStrap.groovy
@@ -0,0 +1,7 @@
+class BootStrap {
+
+    def init = { servletContext ->
+    }
+    def destroy = {
+    }
+}
diff --git a/bbb-web-api/grails-app/init/bbb/web/api/Application.groovy b/bbb-web-api/grails-app/init/bbb/web/api/Application.groovy
new file mode 100755
index 0000000000..5e30092121
--- /dev/null
+++ b/bbb-web-api/grails-app/init/bbb/web/api/Application.groovy
@@ -0,0 +1,10 @@
+package bbb.web.api
+
+import grails.boot.GrailsApp
+import grails.boot.config.GrailsAutoConfiguration
+
+class Application extends GrailsAutoConfiguration {
+    static void main(String[] args) {
+        GrailsApp.run(Application, args)
+    }
+}
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy b/bbb-web-api/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy
new file mode 100755
index 0000000000..a05862a8fe
--- /dev/null
+++ b/bbb-web-api/grails-app/services/org/bigbluebutton/web/services/PresentationService.groovy
@@ -0,0 +1,171 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.web.services
+
+import java.util.concurrent.*;
+import java.lang.InterruptedException
+import org.bigbluebutton.presentation.DocumentConversionService
+import org.bigbluebutton.presentation.UploadedPresentation
+
+class PresentationService {
+
+    static transactional = false
+    DocumentConversionService documentConversionService
+    def presentationDir
+    def testConferenceMock
+    def testRoomMock
+    def testPresentationName
+    def testUploadedPresentation
+    def defaultUploadedPresentation
+    def presentationBaseUrl
+
+    def deletePresentation = { conf, room, filename ->
+        def directory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename)
+        deleteDirectory(directory)
+    }
+
+    def deleteDirectory = { directory ->
+        log.debug "delete = ${directory}"
+        /**
+         * Go through each directory and check if it's not empty.
+         * We need to delete files inside a directory before a
+         * directory can be deleted.
+         **/
+        File[] files = directory.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].isDirectory()) {
+                deleteDirectory(files[i])
+            } else {
+                files[i].delete()
+            }
+        }
+        // Now that the directory is empty. Delete it.
+        directory.delete()
+    }
+
+    def listPresentations = { conf, room ->
+        def presentationsList = []
+        def directory = roomDirectory(conf, room)
+        log.debug "directory ${directory.absolutePath}"
+        if (directory.exists()) {
+            directory.eachFile() { file ->
+                if (file.isDirectory())
+                    presentationsList.add(file.name)
+            }
+        }
+        return presentationsList
+    }
+
+    def getPresentationDir = {
+        return presentationDir
+    }
+
+    def processUploadedPresentation = { uploadedPres ->
+        // Run conversion on another thread.
+        Timer t = new Timer(uploadedPres.getName(), false)
+
+        t.runAfter(1000) {
+            try {
+                documentConversionService.processDocument(uploadedPres)
+            } finally {
+                t.cancel()
+            }
+        }
+    }
+
+    def showSlide(String conf, String room, String presentationName, String id) {
+        new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "slide-${id}.swf")
+    }
+
+    def showSvgImage(String conf, String room, String presentationName, String id) {
+        new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "svgs" + File.separatorChar + "slide${id}.svg")
+    }
+
+    def showPresentation = { conf, room, filename ->
+        new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename + File.separatorChar + "slides.swf")
+    }
+
+    def showThumbnail = { conf, room, presentationName, thumb ->
+        def thumbFile = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
+                "thumbnails" + File.separatorChar + "thumb-${thumb}.png"
+        log.debug "showing $thumbFile"
+
+        new File(thumbFile)
+    }
+
+    def showTextfile = { conf, room, presentationName, textfile ->
+        def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
+                "textfiles" + File.separatorChar + "slide-${textfile}.txt"
+        log.debug "showing $txt"
+
+        new File(txt)
+    }
+
+    def numberOfThumbnails = { conf, room, name ->
+        def thumbDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "thumbnails")
+        thumbDir.listFiles().length
+    }
+
+    def numberOfSvgs = { conf, room, name ->
+        def SvgsDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "svgs")
+        SvgsDir.listFiles().length
+    }
+
+    def numberOfTextfiles = { conf, room, name ->
+        log.debug roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles"
+        def textfilesDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles")
+        textfilesDir.listFiles().length
+    }
+
+    def roomDirectory = { conf, room ->
+        return new File(presentationDir + File.separatorChar + conf + File.separatorChar + room)
+    }
+
+    def testConversionProcess() {
+        File presDir = new File(roomDirectory(testConferenceMock, testRoomMock).absolutePath + File.separatorChar + testPresentationName)
+
+        if (presDir.exists()) {
+            File pres = new File(presDir.getAbsolutePath() + File.separatorChar + testUploadedPresentation)
+            if (pres.exists()) {
+                UploadedPresentation uploadedPres = new UploadedPresentation(testConferenceMock, testRoomMock, testPresentationName);
+                uploadedPres.setUploadedFile(pres);
+                // Run conversion on another thread.
+                new Timer().runAfter(1000)
+                        {
+                            documentConversionService.processDocument(uploadedPres)
+                        }
+            } else {
+                log.error "${pres.absolutePath} does NOT exist"
+            }
+        } else {
+            log.error "${presDir.absolutePath} does NOT exist."
+        }
+
+    }
+}
+
+/*** Helper classes **/
+import java.io.FilenameFilter;
+import java.io.File;
+
+class SvgFilter implements FilenameFilter {
+    public boolean accept(File dir, String name) {
+        return (name.endsWith(".svg"));
+    }
+}
\ No newline at end of file
diff --git a/bbb-web-api/grails-app/views/error.gsp b/bbb-web-api/grails-app/views/error.gsp
new file mode 100755
index 0000000000..9a3bb8aa3d
--- /dev/null
+++ b/bbb-web-api/grails-app/views/error.gsp
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+    <head>
+        <title><g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else></title>
+        <meta name="layout" content="main">
+        <g:if env="development"><asset:stylesheet src="errors.css"/></g:if>
+    </head>
+    <body>
+        <g:if env="development">
+            <g:if test="${Throwable.isInstance(exception)}">
+                <g:renderException exception="${exception}" />
+            </g:if>
+            <g:elseif test="${request.getAttribute('javax.servlet.error.exception')}">
+                <g:renderException exception="${request.getAttribute('javax.servlet.error.exception')}" />
+            </g:elseif>
+            <g:else>
+                <ul class="errors">
+                    <li>An error has occurred</li>
+                    <li>Exception: ${exception}</li>
+                    <li>Message: ${message}</li>
+                    <li>Path: ${path}</li>
+                </ul>
+            </g:else>
+        </g:if>
+        <g:else>
+            <ul class="errors">
+                <li>An error has occurred</li>
+            </ul>
+        </g:else>
+    </body>
+</html>
diff --git a/bbb-web-api/grails-app/views/index.gsp b/bbb-web-api/grails-app/views/index.gsp
new file mode 100755
index 0000000000..7dda996348
--- /dev/null
+++ b/bbb-web-api/grails-app/views/index.gsp
@@ -0,0 +1,79 @@
+<!doctype html>
+<html>
+<head>
+    <meta name="layout" content="main"/>
+    <title>Welcome to Grails</title>
+
+    <asset:link rel="icon" href="favicon.ico" type="image/x-ico" />
+</head>
+<body>
+    <content tag="nav">
+        <li class="dropdown">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Application Status <span class="caret"></span></a>
+            <ul class="dropdown-menu">
+                <li><a href="#">Environment: ${grails.util.Environment.current.name}</a></li>
+                <li><a href="#">App profile: ${grailsApplication.config.grails?.profile}</a></li>
+                <li><a href="#">App version:
+                    <g:meta name="info.app.version"/></a>
+                </li>
+                <li role="separator" class="divider"></li>
+                <li><a href="#">Grails version:
+                    <g:meta name="info.app.grailsVersion"/></a>
+                </li>
+                <li><a href="#">Groovy version: ${GroovySystem.getVersion()}</a></li>
+                <li><a href="#">JVM version: ${System.getProperty('java.version')}</a></li>
+                <li role="separator" class="divider"></li>
+                <li><a href="#">Reloading active: ${grails.util.Environment.reloadingAgentEnabled}</a></li>
+            </ul>
+        </li>
+        <li class="dropdown">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Artefacts <span class="caret"></span></a>
+            <ul class="dropdown-menu">
+                <li><a href="#">Controllers: ${grailsApplication.controllerClasses.size()}</a></li>
+                <li><a href="#">Domains: ${grailsApplication.domainClasses.size()}</a></li>
+                <li><a href="#">Services: ${grailsApplication.serviceClasses.size()}</a></li>
+                <li><a href="#">Tag Libraries: ${grailsApplication.tagLibClasses.size()}</a></li>
+            </ul>
+        </li>
+        <li class="dropdown">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Installed Plugins <span class="caret"></span></a>
+            <ul class="dropdown-menu">
+                <g:each var="plugin" in="${applicationContext.getBean('pluginManager').allPlugins}">
+                    <li><a href="#">${plugin.name} - ${plugin.version}</a></li>
+                </g:each>
+            </ul>
+        </li>
+    </content>
+
+    <div class="svg" role="presentation">
+        <div class="grails-logo-container">
+            <asset:image src="grails-cupsonly-logo-white.svg" class="grails-logo"/>
+        </div>
+    </div>
+
+    <div id="content" role="main">
+        <section class="row colset-2-its">
+            <h1>Welcome to Grails</h1>
+
+            <p>
+                Congratulations, you have successfully started your first Grails application! At the moment
+                this is the default page, feel free to modify it to either redirect to a controller or display
+                whatever content you may choose. Below is a list of controllers that are currently deployed in
+                this application, click on each to execute its default action:
+            </p>
+
+            <div id="controllers" role="navigation">
+                <h2>Available Controllers:</h2>
+                <ul>
+                    <g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
+                        <li class="controller">
+                            <g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link>
+                        </li>
+                    </g:each>
+                </ul>
+            </div>
+        </section>
+    </div>
+
+</body>
+</html>
diff --git a/bbb-web-api/grails-app/views/layouts/main.gsp b/bbb-web-api/grails-app/views/layouts/main.gsp
new file mode 100755
index 0000000000..753706ac65
--- /dev/null
+++ b/bbb-web-api/grails-app/views/layouts/main.gsp
@@ -0,0 +1,51 @@
+<!doctype html>
+<html lang="en" class="no-js">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+    <title>
+        <g:layoutTitle default="Grails"/>
+    </title>
+    <meta name="viewport" content="width=device-width, initial-scale=1"/>
+
+    <asset:stylesheet src="application.css"/>
+
+    <g:layoutHead/>
+</head>
+<body>
+
+    <div class="navbar navbar-default navbar-static-top" role="navigation">
+        <div class="container">
+            <div class="navbar-header">
+                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+                    <span class="sr-only">Toggle navigation</span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                </button>
+                <a class="navbar-brand" href="/#">
+                    <i class="fa grails-icon">
+                        <asset:image src="grails-cupsonly-logo-white.svg"/>
+                    </i> Grails
+                </a>
+            </div>
+            <div class="navbar-collapse collapse" aria-expanded="false" style="height: 0.8px;">
+                <ul class="nav navbar-nav navbar-right">
+                    <g:pageProperty name="page.nav" />
+                </ul>
+            </div>
+        </div>
+    </div>
+
+    <g:layoutBody/>
+
+    <div class="footer" role="contentinfo"></div>
+
+    <div id="spinner" class="spinner" style="display:none;">
+        <g:message code="spinner.alt" default="Loading&hellip;"/>
+    </div>
+
+    <asset:javascript src="application.js"/>
+
+</body>
+</html>
diff --git a/bbb-web-api/grails-app/views/notFound.gsp b/bbb-web-api/grails-app/views/notFound.gsp
new file mode 100755
index 0000000000..4c873baae0
--- /dev/null
+++ b/bbb-web-api/grails-app/views/notFound.gsp
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Page Not Found</title>
+        <meta name="layout" content="main">
+        <g:if env="development"><asset:stylesheet src="errors.css"/></g:if>
+    </head>
+    <body>
+        <ul class="errors">
+            <li>Error: Page Not Found (404)</li>
+            <li>Path: ${request.forwardURI}</li>
+        </ul>
+    </body>
+</html>
diff --git a/bbb-web-api/src/main/groovy/org/bigbluebutton/api/ClientConfigServiceHelperImp.groovy b/bbb-web-api/src/main/groovy/org/bigbluebutton/api/ClientConfigServiceHelperImp.groovy
new file mode 100755
index 0000000000..3e026b96a2
--- /dev/null
+++ b/bbb-web-api/src/main/groovy/org/bigbluebutton/api/ClientConfigServiceHelperImp.groovy
@@ -0,0 +1,46 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.io.File;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientConfigServiceHelperImp implements IClientConfigServiceHelper {
+	private static Logger log = LoggerFactory.getLogger(ClientConfigServiceHelperImp.class);
+
+		
+	public Map<String, String> getPreBuiltConfigs(String dir) {
+		Map<String, String> configs = new HashMap<String, String>();
+		
+		File confDir = new File(dir);
+		if (confDir.isDirectory()) {
+			File[] files = confDir.listFiles();
+			for (int i = 0; i < files.length; i++) {
+				if (! files[i].isDirectory()) {
+					File file = files[i]
+				  configs.put(file.name, file.text)
+				}
+			}		
+		}
+		return configs;
+	}
+	
+}
diff --git a/bbb-web-api/src/main/groovy/org/bigbluebutton/api/RecordingServiceHelperImp.groovy b/bbb-web-api/src/main/groovy/org/bigbluebutton/api/RecordingServiceHelperImp.groovy
new file mode 100755
index 0000000000..55f419f7f9
--- /dev/null
+++ b/bbb-web-api/src/main/groovy/org/bigbluebutton/api/RecordingServiceHelperImp.groovy
@@ -0,0 +1,132 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import groovy.util.XmlSlurper;
+import groovy.util.slurpersupport.GPathResult;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+import org.bigbluebutton.api.domain.Recording;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RecordingServiceHelperImp implements RecordingServiceHelper {
+    private static Logger log = LoggerFactory.getLogger(RecordingServiceHelperImp.class);
+    /*
+    <recording>
+        <id>6e35e3b2778883f5db637d7a5dba0a427f692e91-1398363221956</id>
+        <state>available</state>
+        <published>true</published>
+        <start_time>1398363223514</start_time>
+        <end_time>1398363348994</end_time>
+        <playback>
+            <format>presentation</format>
+            <link>http://example.com/playback/presentation/playback.html?meetingID=6e35e3b2778883f5db637d7a5dba0a427f692e91-1398363221956</link>
+            <processing_time>5429</processing_time>
+            <duration>101014</duration>
+            <extension>
+                ... Any XML element, to be passed through into playback format element.
+            </extension>
+        </playback>
+        <meta>
+            <meetingId>English 101</meetingId>
+            <meetingName>English 101</meetingName>
+            <description>Test recording</description>
+            <title>English 101</title>
+        </meta>
+    </recording>
+    */
+
+    public void writeRecordingInfo(String path, Recording info) {
+        def writer = new StringWriter()
+        def builder = new groovy.xml.MarkupBuilder(writer)
+        def metadataXml = builder.recording {
+            builder.id(info.getId())
+            builder.state(info.getState())
+            builder.published(info.isPublished())
+            builder.start_time(info.getStartTime())
+            builder.end_time(info.getEndTime())
+            if ( info.getPlaybackFormat() == null ) {
+                builder.playback()
+            } else {
+                builder.playback {
+                    builder.format(info.getPlaybackFormat())
+                    builder.link(info.getPlaybackLink())
+                    builder.duration(info.getPlaybackDuration())
+                    builder.extension(info.getPlaybackExtensions())
+                }
+            }
+            Map<String,String> metainfo = info.getMetadata();
+            builder.meta{
+                metainfo.keySet().each { key ->
+                    builder."$key"(metainfo.get(key))
+                }
+            }
+        }
+        def xmlEventFile = new File(path + File.separatorChar + "metadata.xml")
+        xmlEventFile.write writer.toString()
+    }
+
+    public Recording getRecordingInfo(File dir) {
+        if (dir.isDirectory()) {
+            try {
+                File file = new File(dir.getPath() + File.separatorChar + "metadata.xml");
+                if ( file ) {
+                    def recording = new XmlSlurper().parse(file);
+                    return getInfo(recording);
+                }
+            } catch ( FileNotFoundException e) {
+                // Do nothing, just return null
+            } catch ( Exception e) {
+                log.debug(e.getMessage())
+            }
+        }
+        return null;
+    }
+
+    private Recording getInfo(GPathResult rec) {
+        Recording r = new Recording();
+        r.setId(rec.id.text());
+        r.setState(rec.state.text());
+        r.setPublished(Boolean.parseBoolean(rec.published.text()));
+        r.setStartTime(rec.start_time.text());
+        r.setEndTime(rec.end_time.text());
+        if ( !rec.playback.text().equals("") ) {
+            r.setPlaybackFormat(rec.playback.format.text());
+            r.setPlaybackLink(rec.playback.link.text());
+            r.setPlaybackDuration(rec.playback.duration.text());
+        }
+
+/*
+        Commenting this out to see if this is causing memory to hang around resulting in
+        OOM in tomcat7 (ralam july 23, 2015)
+        r.setPlaybackExtensions(rec.playback.extension.children());
+*/		
+        Map<String, String> meta = new HashMap<String, String>();
+        rec.meta.children().each { anode ->
+            meta.put(anode.name().toString(), anode.text().toString());
+        }
+        r.setMetadata(meta);
+        return r;
+    }
+
+}
diff --git a/bbb-web-api/src/main/groovy/org/bigbluebutton/presentation/GeneratedSlidesInfoHelperImp.groovy b/bbb-web-api/src/main/groovy/org/bigbluebutton/presentation/GeneratedSlidesInfoHelperImp.groovy
new file mode 100755
index 0000000000..6f8c953dc0
--- /dev/null
+++ b/bbb-web-api/src/main/groovy/org/bigbluebutton/presentation/GeneratedSlidesInfoHelperImp.groovy
@@ -0,0 +1,52 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation
+
+/*
+ * Helper class to get info about the generated slides. Easier to
+ * generate XML in Groovy.
+ */
+public class GeneratedSlidesInfoHelperImp implements GeneratedSlidesInfoHelper {
+
+	 /*
+	  * Returns an XML string containing the URL for the slides and thumbails.
+	  */
+	public String generateUploadedPresentationInfo(UploadedPresentation pres) {
+		def writer = new java.io.StringWriter()
+		def builder = new groovy.xml.MarkupBuilder(writer)
+		        		
+		def uploadedpresentation = builder.uploadedpresentation {        
+		    conference(id:pres.meetingId, room:pres.meetingId) {
+		       presentation(name:pres.presentationName) {
+		          slides(count:pres.numberOfPages) {
+		             for (def i = 1; i <= pres.numberOfPages; i++) {
+		                slide(number:"${i}", name:"slide/${i}", thumb:"thumbnail/${i}")
+		             }
+		          }
+		       }
+			}
+		}
+	
+		return writer.toString()		
+	}
+	
+	
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/ApiErrors.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/ApiErrors.java
new file mode 100755
index 0000000000..1af0cc0ba3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/ApiErrors.java
@@ -0,0 +1,78 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.util.ArrayList;
+
+public class ApiErrors {
+	private ArrayList<String[]> errors = new ArrayList<String[]>();
+
+	public void missingParamError(String param) {
+		errors.add(new String[] {"MissingParam", "You did not pass a " + param + " parameter."});
+	}
+
+	public void checksumError() {
+		errors.add( new String[] {"checksumError", "You did not pass the checksum security check"});
+	}
+
+	public void nonUniqueMeetingIdError() {
+		errors.add(new String[] {"NotUniqueMeetingID", "A meeting already exists with that meeting ID.  Please use a different meeting ID."});
+	}
+
+	public void invalidMeetingIdError() {
+		errors.add(new String[] {"invalidMeetingId", "The meeting ID that you supplied did not match any existing meetings"});
+	}
+
+	public void meetingForciblyEndedError() {
+		errors.add(new String[] {"meetingForciblyEnded", "You can not re-join a meeting that has already been forcibly ended."});
+	}
+
+	public void invalidPasswordError() {
+		errors.add(new String[] {"invalidPassword", "The password you submitted is not valid."});
+	}
+
+	public void mismatchCreateTimeParam() {
+		errors.add(new String[] {"mismatchCreateTime", "The createTime parameter submitted mismatches with the current meeting."});
+	}
+
+	public void recordingNotFound() {
+		errors.add(new String[] {"recordingNotFound", "We could not find a recording with that recordID."});
+	}
+
+	public void noConfigFoundForToken(String token) {
+		errors.add(new String[] {"configNotFound", "We could not find a config for token [" + token + "]."});
+	}
+
+	public void noConfigFound() {
+		errors.add(new String[] {"noConfigFound", "We could not find a config for this request."});
+	}
+
+	public void maxParticipantsReached() {
+		errors.add(new String[] {"maxParticipantsReached", "The number of participants allowed for this meeting has been reached."});
+	}
+
+	public boolean hasErrors() {
+		return errors.size() > 0;
+	}
+
+	public ArrayList<String[]> getErrors() {
+		return errors;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/ClientConfigService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/ClientConfigService.java
new file mode 100755
index 0000000000..c06f635980
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/ClientConfigService.java
@@ -0,0 +1,59 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientConfigService {
+	private static Logger log = LoggerFactory.getLogger(ClientConfigService.class);
+	
+	private String configDir = "/var/bigbluebutton/configs";
+  private IClientConfigServiceHelper helper;
+
+  private Map<String, String> configs = new HashMap<String, String>();
+  
+  public void init() {
+  	configs = getAllConfigs();
+  }
+  
+  public String getConfig(String id) {
+  	return configs.get(id);
+  }
+  
+	private Map<String, String> getAllConfigs(){
+		return helper.getPreBuiltConfigs(configDir);
+	}
+		
+	public void setConfigDir(String dir) {
+		configDir = dir;
+	}
+	
+	public void setClientConfigServiceHelper(IClientConfigServiceHelper r) {
+		helper = r;
+	} 
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/IClientConfigServiceHelper.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/IClientConfigServiceHelper.java
new file mode 100755
index 0000000000..fd74c3fa58
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/IClientConfigServiceHelper.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.util.Map;
+
+public interface IClientConfigServiceHelper {
+	public Map<String, String> getPreBuiltConfigs(String dir);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/MeetingService.java
new file mode 100755
index 0000000000..79c92b4373
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/MeetingService.java
@@ -0,0 +1,941 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.api;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.bigbluebutton.api.domain.Meeting;
+import org.bigbluebutton.api.domain.Playback;
+import org.bigbluebutton.api.domain.Recording;
+import org.bigbluebutton.api.domain.User;
+import org.bigbluebutton.api.domain.UserSession;
+import org.bigbluebutton.api.messaging.MessageListener;
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom;
+import org.bigbluebutton.api.messaging.messages.CreateMeeting;
+import org.bigbluebutton.api.messaging.messages.EndBreakoutRoom;
+import org.bigbluebutton.api.messaging.messages.EndMeeting;
+import org.bigbluebutton.api.messaging.messages.IMessage;
+import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
+import org.bigbluebutton.api.messaging.messages.MeetingEnded;
+import org.bigbluebutton.api.messaging.messages.MeetingStarted;
+import org.bigbluebutton.api.messaging.messages.RegisterUser;
+import org.bigbluebutton.api.messaging.messages.RemoveExpiredMeetings;
+import org.bigbluebutton.api.messaging.messages.UserJoined;
+import org.bigbluebutton.api.messaging.messages.UserJoinedVoice;
+import org.bigbluebutton.api.messaging.messages.UserLeft;
+import org.bigbluebutton.api.messaging.messages.UserLeftVoice;
+import org.bigbluebutton.api.messaging.messages.UserListeningOnly;
+import org.bigbluebutton.api.messaging.messages.UserSharedWebcam;
+import org.bigbluebutton.api.messaging.messages.UserStatusChanged;
+import org.bigbluebutton.api.messaging.messages.UserUnsharedWebcam;
+import org.bigbluebutton.presentation.PresentationUrlDownloadService;
+import org.bigbluebutton.api.messaging.messages.StunTurnInfoRequested;
+import org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask;
+import org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask;
+import org.bigbluebutton.web.services.turn.StunServer;
+import org.bigbluebutton.web.services.turn.StunTurnService;
+import org.bigbluebutton.web.services.turn.TurnEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+public class MeetingService implements MessageListener {
+    private static Logger log = LoggerFactory.getLogger(MeetingService.class);
+
+    private BlockingQueue<IMessage> receivedMessages = new LinkedBlockingQueue<IMessage>();
+    private volatile boolean processMessage = false;
+
+    private final Executor msgProcessorExec = Executors
+            .newSingleThreadExecutor();
+    private final Executor runExec = Executors.newSingleThreadExecutor();
+
+    /**
+     * http://ria101.wordpress.com/2011/12/12/concurrenthashmap-avoid-a-common-
+     * misuse/
+     */
+    private final ConcurrentMap<String, Meeting> meetings;
+    private final ConcurrentMap<String, UserSession> sessions;
+
+    private int defaultMeetingExpireDuration = 1;
+    private int defaultMeetingCreateJoinDuration = 5;
+    private RecordingService recordingService;
+    private MessagingService messagingService;
+    private ExpiredMeetingCleanupTimerTask cleaner;
+    private RegisteredUserCleanupTimerTask registeredUserCleaner;
+    private StunTurnService stunTurnService;
+    private boolean removeMeetingWhenEnded = false;
+
+    private ParamsProcessorUtil paramsProcessorUtil;
+    private PresentationUrlDownloadService presDownloadService;
+
+    public MeetingService() {
+        meetings = new ConcurrentHashMap<String, Meeting>(8, 0.9f, 1);
+        sessions = new ConcurrentHashMap<String, UserSession>(8, 0.9f, 1);
+    }
+
+    public void addUserSession(String token, UserSession user) {
+        sessions.put(token, user);
+    }
+
+    public void registerUser(String meetingID, String internalUserId,
+            String fullname, String role, String externUserID, String authToken, String avatarURL) {
+        handle(new RegisterUser(meetingID, internalUserId, fullname, role,
+                externUserID, authToken, avatarURL));
+    }
+
+    public UserSession getUserSession(String token) {
+        return sessions.get(token);
+    }
+
+    public UserSession removeUserSession(String token) {
+        UserSession user = sessions.remove(token);
+        if (user != null) {
+            log.debug("Found user [" + user.fullname + "] token=[" + token
+                    + "] to meeting [" + user.meetingID + "]");
+        }
+        return user;
+    }
+
+    /**
+     * Remove the meetings that have ended from the list of running meetings.
+     */
+    public void removeExpiredMeetings() {
+        handle(new RemoveExpiredMeetings());
+    }
+
+    /**
+     * Remove registered users who did not successfully joined the meeting.
+     */
+    public void purgeRegisteredUsers() {
+        for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) {
+            Long now = System.nanoTime();
+            Meeting meeting = entry.getValue();
+
+            ConcurrentMap<String, User> users = meeting.getUsersMap();
+
+            for (AbstractMap.Entry<String, Long> registeredUser : meeting.getRegisteredUsers().entrySet()) {
+                String registeredUserID = registeredUser.getKey();
+                Long registeredUserDate = registeredUser.getValue();
+
+                long registrationTime = registeredUserDate.longValue();
+                long elapsedTime = now - registrationTime;
+                if ( elapsedTime >= 60000 && !users.containsKey(registeredUserID)) {
+                    meeting.userUnregistered(registeredUserID);
+                }
+            }
+        }
+        handle(new RemoveExpiredMeetings());
+    }
+
+    private void kickOffProcessingOfRecording(Meeting m) {
+        if (m.isRecord() && m.getNumUsers() == 0) {
+            Map<String, Object> logData = new HashMap<String, Object>();
+            logData.put("meetingId", m.getInternalId());
+            logData.put("externalMeetingId", m.getExternalId());
+            logData.put("name", m.getName());
+            logData.put("event", "kick_off_ingest_and_processing");
+            logData.put("description", "Start processing of recording.");
+
+            Gson gson = new Gson();
+            String logStr = gson.toJson(logData);
+
+            log.info("Initiate recording processing: data={}", logStr);
+
+            processRecording(m.getInternalId());
+        }
+    }
+
+    private void processMeetingForRemoval(Meeting m) {
+        kickOffProcessingOfRecording(m);
+        destroyMeeting(m.getInternalId());
+        meetings.remove(m.getInternalId());
+        removeUserSessions(m.getInternalId());
+    }
+
+    private void removeUserSessions(String meetingId) {
+        Iterator<Map.Entry<String, UserSession>> iterator = sessions.entrySet()
+                .iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, UserSession> entry = iterator.next();
+            UserSession userSession = entry.getValue();
+
+            if (userSession.meetingID.equals(meetingId)) {
+                iterator.remove();
+            }
+        }
+    }
+
+    private void checkAndRemoveExpiredMeetings() {
+        for (Meeting m : meetings.values()) {
+            if (m.hasExpired(defaultMeetingExpireDuration)) {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("event", "removing_meeting");
+                logData.put("description", "Meeting has expired.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+                log.info("Removing expired meeting: data={}", logStr);
+
+                processMeetingForRemoval(m);
+                continue;
+            }
+
+            if (m.isForciblyEnded()) {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("event", "removing_meeting");
+                logData.put("description", "Meeting forcefully ended.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+
+                log.info("Removing ended meeting: data={}", logStr);
+                processMeetingForRemoval(m);
+                continue;
+            }
+
+            if (m.wasNeverJoined(defaultMeetingCreateJoinDuration)) {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("event", "removing_meeting");
+                logData.put("description", "Meeting has not been joined.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+
+                log.info("Removing un-joined meeting: data={}", logStr);
+
+                destroyMeeting(m.getInternalId());
+                meetings.remove(m.getInternalId());
+                removeUserSessions(m.getInternalId());
+                continue;
+            }
+
+            if (m.hasExceededDuration()) {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("event", "removing_meeting");
+                logData.put("description", "Meeting exceeded duration.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+
+                log.info("Removing past duration meeting: data={}", logStr);
+
+                endMeeting(m.getInternalId());
+            }
+        }
+    }
+
+    private void destroyMeeting(String meetingID) {
+        messagingService.destroyMeeting(meetingID);
+    }
+
+    public Collection<Meeting> getMeetings() {
+        return meetings.isEmpty() ? Collections.<Meeting> emptySet()
+                : Collections.unmodifiableCollection(meetings.values());
+    }
+
+    public Collection<UserSession> getSessions() {
+        return sessions.isEmpty() ? Collections.<UserSession> emptySet()
+                : Collections.unmodifiableCollection(sessions.values());
+    }
+
+    public void createMeeting(Meeting m) {
+        handle(new CreateMeeting(m));
+    }
+
+    private void handleCreateMeeting(Meeting m) {
+        meetings.put(m.getInternalId(), m);
+        if (m.isRecord()) {
+            Map<String, String> metadata = new LinkedHashMap<String, String>();
+            metadata.putAll(m.getMetadata());
+            // TODO: Need a better way to store these values for recordings
+            metadata.put("meetingId", m.getExternalId());
+            metadata.put("meetingName", m.getName());
+
+            messagingService.recordMeetingInfo(m.getInternalId(), metadata);
+        }
+
+        Map<String, Object> logData = new HashMap<String, Object>();
+        logData.put("meetingId", m.getInternalId());
+        logData.put("externalMeetingId", m.getExternalId());
+        logData.put("name", m.getName());
+        logData.put("duration", m.getDuration());
+        logData.put("record", m.isRecord());
+        logData.put("event", "create_meeting");
+        logData.put("description", "Create meeting.");
+
+        Gson gson = new Gson();
+        String logStr = gson.toJson(logData);
+
+        log.info("Create meeting: data={}", logStr);
+
+        messagingService.createMeeting(m.getInternalId(), m.getExternalId(),
+                m.getName(), m.isRecord(), m.getTelVoice(), m.getDuration(),
+                m.getAutoStartRecording(), m.getAllowStartStopRecording(),
+                m.getModeratorPassword(), m.getViewerPassword(),
+                m.getCreateTime(), formatPrettyDate(m.getCreateTime()), m.isBreakout());
+    }
+
+    private String formatPrettyDate(Long timestamp) {
+        return new Date(timestamp).toString();
+    }
+
+    private void processCreateMeeting(CreateMeeting message) {
+        handleCreateMeeting(message.meeting);
+    }
+
+    private void processRegisterUser(RegisterUser message) {
+        messagingService.registerUser(message.meetingID,
+                message.internalUserId, message.fullname, message.role,
+                message.externUserID, message.authToken, message.avatarURL);
+    }
+
+    public String addSubscription(String meetingId, String event,
+            String callbackURL) {
+        String sid = messagingService.storeSubscription(meetingId, event,
+                callbackURL);
+        return sid;
+    }
+
+    public boolean removeSubscription(String meetingId, String subscriptionId) {
+        return messagingService.removeSubscription(meetingId, subscriptionId);
+    }
+
+    public List<Map<String, String>> listSubscriptions(String meetingId) {
+        return messagingService.listSubscriptions(meetingId);
+    }
+    
+    public Meeting getMeeting(String meetingId) {
+        return getMeeting(meetingId, false);
+    }
+
+    public Meeting getMeeting(String meetingId, Boolean exactMatch) {
+        if (meetingId == null)
+            return null;
+        for (String key : meetings.keySet()) {
+            if ((!exactMatch && key.startsWith(meetingId))
+                    || (exactMatch && key.equals(meetingId)))
+                return (Meeting) meetings.get(key);
+        }
+
+        return null;
+    }
+
+    public Collection<Meeting> getMeetingsWithId(String meetingId) {
+        if (meetingId == null)
+            return Collections.<Meeting> emptySet();
+
+        Collection<Meeting> m = new HashSet<Meeting>();
+
+        for (String key : meetings.keySet()) {
+            if (key.startsWith(meetingId))
+                m.add(meetings.get(key));
+        }
+
+        return m;
+    }
+
+    public Meeting getNotEndedMeetingWithId(String meetingId) {
+        if (meetingId == null)
+            return null;
+        for (String key : meetings.keySet()) {
+            if (key.startsWith(meetingId)) {
+                Meeting m = (Meeting) meetings.get(key);
+                if (!m.isForciblyEnded())
+                    return m;
+            }
+        }
+
+        return null;
+    }
+
+    public Map<String, Recording> getRecordings(List<String> idList,
+            List<String> states) {
+        List<Recording> recsList = recordingService.getRecordings(idList,
+                states);
+        Map<String, Recording> recs = reorderRecordings(recsList);
+        return recs;
+    }
+
+    public Map<String, Recording> filterRecordingsByMetadata(
+            Map<String, Recording> recordings,
+            Map<String, String> metadataFilters) {
+        return recordingService.filterRecordingsByMetadata(recordings,
+                metadataFilters);
+    }
+
+    public Map<String, Recording> reorderRecordings(List<Recording> olds) {
+        Map<String, Recording> map = new HashMap<String, Recording>();
+        for (Recording r : olds) {
+            if (!map.containsKey(r.getId())) {
+                Map<String, String> meta = r.getMetadata();
+                String mid = meta.remove("meetingId");
+                String name = meta.remove("meetingName");
+
+                r.setMeetingID(mid);
+                r.setName(name);
+
+                ArrayList<Playback> plays = new ArrayList<Playback>();
+
+                if (r.getPlaybackFormat() != null) {
+                    plays.add(new Playback(r.getPlaybackFormat(), r
+                            .getPlaybackLink(), getDurationRecording(
+                            r.getPlaybackDuration(), r.getEndTime(),
+                            r.getStartTime()), r.getPlaybackExtensions()));
+                }
+
+                r.setPlaybacks(plays);
+                map.put(r.getId(), r);
+            } else {
+                Recording rec = map.get(r.getId());
+                rec.getPlaybacks().add(
+                        new Playback(r.getPlaybackFormat(),
+                                r.getPlaybackLink(), getDurationRecording(
+                                        r.getPlaybackDuration(),
+                                        r.getEndTime(), r.getStartTime()), r
+                                        .getPlaybackExtensions()));
+            }
+        }
+
+        return map;
+    }
+
+    private int getDurationRecording(String playbackDuration, String end,
+            String start) {
+        int duration;
+        try {
+            if (!playbackDuration.equals("")) {
+                duration = (int) Math
+                        .ceil((Long.parseLong(playbackDuration)) / 60000.0);
+            } else {
+                duration = (int) Math.ceil((Long.parseLong(end) - Long
+                        .parseLong(start)) / 60000.0);
+            }
+        } catch (Exception e) {
+            log.debug(e.getMessage());
+            duration = 0;
+        }
+
+        return duration;
+    }
+
+    public boolean existsAnyRecording(List<String> idList) {
+        return recordingService.existAnyRecording(idList);
+    }
+
+    public void setPublishRecording(List<String> idList, boolean publish) {
+        for (String id : idList) {
+            if (publish) {
+              recordingService.changeState(id, Recording.STATE_PUBLISHED);
+            } else {
+              recordingService.changeState(id, Recording.STATE_UNPUBLISHED);
+	    }
+          }
+	}
+	
+	public void deleteRecordings(ArrayList<String> idList){
+          for (String id : idList) {
+            recordingService.changeState(id, Recording.STATE_DELETED);
+        }
+    }
+
+    public void processRecording(String meetingId) {
+        recordingService.startIngestAndProcessing(meetingId);
+    }
+
+    public boolean isMeetingWithVoiceBridgeExist(String voiceBridge) {
+        /*
+         * Collection<Meeting> confs = meetings.values(); for (Meeting c :
+         * confs) { if (voiceBridge == c.getVoiceBridge()) { return true; } }
+         */return false;
+    }
+
+    public void send(String channel, String message) {
+        messagingService.send(channel, message);
+    }
+
+    public void createdPolls(String meetingId, String title, String question,
+            String questionType, ArrayList<String> answers) {
+        messagingService.sendPolls(meetingId, title, question, questionType,
+                answers);
+    }
+
+    public void endMeeting(String meetingId) {
+        handle(new EndMeeting(meetingId));
+    }
+
+    private void processCreateBreakoutRoom(CreateBreakoutRoom message) {
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("name", message.name);
+        params.put("breakoutId", message.breakoutId);
+        params.put("meetingID", message.parentId);
+        params.put("isBreakout", "true");
+        params.put("attendeePW", message.viewerPassword);
+        params.put("moderatorPW", message.moderatorPassword);
+        params.put("voiceBridge", message.voiceConfId);
+        params.put("duration", message.durationInMinutes.toString());
+
+        Meeting breakout = paramsProcessorUtil.processCreateParams(params);
+
+        handleCreateMeeting(breakout);
+
+        presDownloadService.downloadAndProcessDocument(
+                message.defaultPresentationURL, breakout.getInternalId());
+    }
+
+    private void processEndBreakoutRoom(EndBreakoutRoom message) {
+        processEndMeeting(new EndMeeting(message.breakoutId));
+    }
+
+    private void processEndMeeting(EndMeeting message) {
+        messagingService.endMeeting(message.meetingId);
+
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            m.setForciblyEnded(true);
+            if (removeMeetingWhenEnded) {
+                processRecording(m.getInternalId());
+                destroyMeeting(m.getInternalId());
+                meetings.remove(m.getInternalId());
+                removeUserSessions(m.getInternalId());
+            }
+        }
+    }
+
+    public void addUserCustomData(String meetingId, String userID,
+            Map<String, String> userCustomData) {
+        Meeting m = getMeeting(meetingId);
+        if (m != null) {
+            m.addUserCustomData(userID, userCustomData);
+        }
+    }
+
+    private void meetingStarted(MeetingStarted message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            if (m.getStartTime() == 0) {
+                long now = System.currentTimeMillis();
+                m.setStartTime(now);
+
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("duration", m.getDuration());
+                logData.put("record", m.isRecord());
+                logData.put("isBreakout", m.isBreakout());
+                logData.put("event", "meeting_started");
+                logData.put("description", "Meeting has started.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+            } else {
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("duration", m.getDuration());
+                logData.put("record", m.isRecord());
+                logData.put("isBreakout", m.isBreakout());
+                logData.put("event", "meeting_restarted");
+                logData.put("description", "Meeting has restarted.");
+
+				Gson gson = new Gson();
+				String logStr =  gson.toJson(logData);
+				
+				log.info("Meeting restarted: data={}", logStr);
+			}
+			return;
+		}
+	}
+
+    private void meetingDestroyed(MeetingDestroyed message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            long now = System.currentTimeMillis();
+            m.setEndTime(now);
+
+            Map<String, Object> logData = new HashMap<String, Object>();
+            logData.put("meetingId", m.getInternalId());
+            logData.put("externalMeetingId", m.getExternalId());
+            logData.put("name", m.getName());
+            logData.put("duration", m.getDuration());
+            logData.put("record", m.isRecord());
+            logData.put("event", "meeting_destroyed");
+            logData.put("description", "Meeting has been destroyed.");
+
+            Gson gson = new Gson();
+            String logStr = gson.toJson(logData);
+
+            log.info("Meeting destroyed: data={}", logStr);
+
+            return;
+        }
+    }
+
+    private void meetingEnded(MeetingEnded message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            long now = System.currentTimeMillis();
+            m.setEndTime(now);
+
+            Map<String, Object> logData = new HashMap<String, Object>();
+            logData.put("meetingId", m.getInternalId());
+            logData.put("externalMeetingId", m.getExternalId());
+            logData.put("name", m.getName());
+            logData.put("duration", m.getDuration());
+            logData.put("record", m.isRecord());
+            logData.put("event", "meeting_destroyed");
+            logData.put("description", "Meeting has been destroyed.");
+
+            Gson gson = new Gson();
+            String logStr =  gson.toJson(logData);
+
+            log.info("Meeting destroyed: data={}", logStr);
+
+            return;
+        }
+    }
+
+    private void userJoined(UserJoined message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            if (m.getNumUsers() == 0) {
+                // First user joins the meeting. Reset the end time to zero
+                // in case the meeting has been rejoined.
+                m.setEndTime(0);
+            }
+
+            User user = new User(message.userId, message.externalUserId,
+                    message.name, message.role, message.avatarURL);
+            m.userJoined(user);
+
+            Map<String, Object> logData = new HashMap<String, Object>();
+            logData.put("meetingId", m.getInternalId());
+            logData.put("externalMeetingId", m.getExternalId());
+            logData.put("name", m.getName());
+            logData.put("userId", message.userId);
+            logData.put("externalUserId", user.getExternalUserId());
+            logData.put("username", user.getFullname());
+            logData.put("role", user.getRole());
+            logData.put("event", "user_joined_message");
+            logData.put("description", "User had joined the meeting.");
+				
+				Gson gson = new Gson();
+		        String logStr =  gson.toJson(logData);
+				
+				log.info("User left meeting: data={}", logStr);
+				
+				return;
+			}
+			return;
+		}
+
+    private void userLeft(UserLeft message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.userLeft(message.userId);
+            if (user != null) {
+
+                Map<String, Object> logData = new HashMap<String, Object>();
+                logData.put("meetingId", m.getInternalId());
+                logData.put("externalMeetingId", m.getExternalId());
+                logData.put("name", m.getName());
+                logData.put("userId", message.userId);
+                logData.put("externalUserId", user.getExternalUserId());
+                logData.put("username", user.getFullname());
+                logData.put("role", user.getRole());
+                logData.put("event", "user_left_message");
+                logData.put("description", "User had left the meeting.");
+
+                Gson gson = new Gson();
+                String logStr = gson.toJson(logData);
+
+                log.info("User left meeting: data={}", logStr);
+
+                if (m.getNumUsers() == 0) {
+                    // Last user the meeting. Mark this as the time
+                    // the meeting ended.
+                    m.setEndTime(System.currentTimeMillis());
+                }
+
+                Long userRegistered = m.userUnregistered(message.userId);
+                if (userRegistered != null) {
+                    log.info("User unregistered from meeting");
+                } else {
+                    log.info("User was not unregistered from meeting because it was not found");
+                }
+
+                return;
+            }
+
+            return;
+        }
+    }
+
+	private void updatedStatus(UserStatusChanged message) {
+		Meeting m = getMeeting(message.meetingId);
+		if (m != null) {
+			User user = m.getUserById(message.userId);
+			if(user != null){
+				user.setStatus(message.status, message.value);
+				return;
+			}
+			return;
+		}
+	}
+
+
+	@Override
+  public void handle(IMessage message) {
+			receivedMessages.add(message);    
+  }
+	
+	
+	public void setParamsProcessorUtil(ParamsProcessorUtil util) {
+	  this.paramsProcessorUtil = util; 
+	}
+	
+	public void setPresDownloadService(PresentationUrlDownloadService presDownloadService) {
+	  this.presDownloadService = presDownloadService;
+	}
+
+    private void processStunTurnInfoRequested (StunTurnInfoRequested message) {
+        Set<StunServer> stuns = stunTurnService.getStunServers();
+        log.info("\nhere are the stuns:");
+        for(StunServer s : stuns) {
+            log.info("a stun: " + s.url);
+        }
+        Set<TurnEntry> turns = stunTurnService.getStunAndTurnServersFor(message.internalUserId);
+        log.info("\nhere are the (" + turns.size() +") turns for internalUserId:" + message.internalUserId);
+        for(TurnEntry t : turns) {
+            log.info("a turn: " + t.url + "username/pass=" + t.username + '/' + t.password);
+        }
+        messagingService.sendStunTurnInfo(message.meetingId, message.internalUserId, stuns, turns);
+    }
+
+
+    public void userJoinedVoice(UserJoinedVoice message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.getUserById(message.userId);
+            if (user != null) {
+                user.setVoiceJoined(true);
+                return;
+            }
+            return;
+        }
+    }
+
+    public void userLeftVoice(UserLeftVoice message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.getUserById(message.userId);
+            if (user != null) {
+                user.setVoiceJoined(false);
+                return;
+            }
+            return;
+        }
+    }
+
+    public void userListeningOnly(UserListeningOnly message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.getUserById(message.userId);
+            if (user != null) {
+                user.setListeningOnly(message.listenOnly);
+                return;
+            }
+            return;
+        }
+    }
+
+    public void userSharedWebcam(UserSharedWebcam message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.getUserById(message.userId);
+            if (user != null) {
+                user.addStream(message.stream);
+                return;
+            }
+            return;
+        }
+    }
+
+    public void userUnsharedWebcam(UserUnsharedWebcam message) {
+        Meeting m = getMeeting(message.meetingId);
+        if (m != null) {
+            User user = m.getUserById(message.userId);
+            if (user != null) {
+                user.removeStream(message.stream);
+                return;
+            }
+            return;
+        }
+    }
+
+    private void processMessage(final IMessage message) {
+        Runnable task = new Runnable() {
+            public void run() {
+                if (message instanceof MeetingStarted) {
+                    meetingStarted((MeetingStarted) message);
+                } else if (message instanceof MeetingDestroyed) {
+                    meetingDestroyed((MeetingDestroyed) message);
+                } else if (message instanceof MeetingEnded) {
+                    meetingEnded((MeetingEnded) message);
+                } else if (message instanceof UserJoined) {
+                    userJoined((UserJoined) message);
+                } else if (message instanceof UserLeft) {
+                    userLeft((UserLeft) message);
+                } else if (message instanceof UserStatusChanged) {
+                    updatedStatus((UserStatusChanged) message);
+                } else if (message instanceof UserJoinedVoice) {
+                    userJoinedVoice((UserJoinedVoice) message);
+                } else if (message instanceof UserLeftVoice) {
+                    userLeftVoice((UserLeftVoice) message);
+                } else if (message instanceof UserListeningOnly) {
+                    userListeningOnly((UserListeningOnly) message);
+                } else if (message instanceof UserSharedWebcam) {
+                    userSharedWebcam((UserSharedWebcam) message);
+                } else if (message instanceof UserUnsharedWebcam) {
+                    userUnsharedWebcam((UserUnsharedWebcam) message);
+                } else if (message instanceof RemoveExpiredMeetings) {
+                    checkAndRemoveExpiredMeetings();
+                } else if (message instanceof CreateMeeting) {
+                    processCreateMeeting((CreateMeeting) message);
+                } else if (message instanceof EndMeeting) {
+                    processEndMeeting((EndMeeting) message);
+                } else if (message instanceof RegisterUser) {
+                    processRegisterUser((RegisterUser) message);
+                }   else if (message instanceof CreateBreakoutRoom) {
+                    processCreateBreakoutRoom((CreateBreakoutRoom) message);
+                } else if (message instanceof EndBreakoutRoom) {
+                    processEndBreakoutRoom((EndBreakoutRoom) message);
+                } else if (message instanceof StunTurnInfoRequested) {
+                    processStunTurnInfoRequested((StunTurnInfoRequested) message);
+                } else if (message instanceof CreateBreakoutRoom) {
+                    processCreateBreakoutRoom((CreateBreakoutRoom) message);
+                } else if (message instanceof EndBreakoutRoom) {
+                    processEndBreakoutRoom((EndBreakoutRoom) message);
+                }
+            }
+        };
+
+        runExec.execute(task);
+    }
+    
+
+    public void start() {
+        log.info("Starting Meeting Service.");
+        try {
+            processMessage = true;
+            Runnable messageReceiver = new Runnable() {
+                public void run() {
+                    while (processMessage) {
+                        try {
+                            IMessage msg = receivedMessages.take();
+                            processMessage(msg);
+                        } catch (InterruptedException e) {
+                            // TODO Auto-generated catch block
+                            e.printStackTrace();
+                        } catch (Exception e) {
+                            log.error("Handling unexpected exception [{}]",
+                                    e.toString());
+                        }
+                    }
+                }
+            };
+
+            msgProcessorExec.execute(messageReceiver);
+        } catch (Exception e) {
+            log.error("Error PRocessing Message");
+        }
+    }
+
+    public void stop() {
+        processMessage = false;
+        cleaner.stop();
+        registeredUserCleaner.stop();
+    }
+
+    public void setDefaultMeetingCreateJoinDuration(int expiration) {
+        this.defaultMeetingCreateJoinDuration = expiration;
+    }
+
+    public void setDefaultMeetingExpireDuration(int meetingExpiration) {
+        this.defaultMeetingExpireDuration = meetingExpiration;
+    }
+
+    public void setRecordingService(RecordingService s) {
+        recordingService = s;
+    }
+
+    public void setMessagingService(MessagingService mess) {
+        messagingService = mess;
+    }
+
+    public void setExpiredMeetingCleanupTimerTask(ExpiredMeetingCleanupTimerTask c) {
+        cleaner = c;
+        cleaner.setMeetingService(this);
+        cleaner.start();
+    }
+
+    public void setRemoveMeetingWhenEnded(boolean s) {
+        removeMeetingWhenEnded = s;
+    }
+
+    public void setRegisteredUserCleanupTimerTask(RegisteredUserCleanupTimerTask c) {
+        registeredUserCleaner = c;
+        registeredUserCleaner.setMeetingService(this);
+        registeredUserCleaner.start();
+    }
+
+    public void setStunTurnService(StunTurnService s) { stunTurnService = s; }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
new file mode 100755
index 0000000000..0a533e1ee8
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
@@ -0,0 +1,780 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
+import org.bigbluebutton.api.domain.Meeting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.methods.*;
+
+public class ParamsProcessorUtil {
+    private static Logger log = LoggerFactory.getLogger(ParamsProcessorUtil.class);
+
+    private final String URLDECODER_SEPARATOR=",";
+    private final String FILTERDECODER_SEPARATOR_ELEMENTS=":";
+    private final String FILTERDECODER_SEPARATOR_OPERATORS="\\|";
+
+    private String apiVersion;
+    private boolean serviceEnabled = false;
+    private String securitySalt;
+    private int defaultMaxUsers = 20;
+    private String defaultWelcomeMessage;
+    private String defaultWelcomeMessageFooter;
+    private String defaultDialAccessNumber;
+    private String testVoiceBridge;
+    private String testConferenceMock;
+    private String defaultLogoutUrl;
+    private String defaultServerUrl;
+    private int defaultNumDigitsForTelVoice;
+    private String defaultClientUrl;
+    private String defaultAvatarURL;
+    private String defaultConfigURL;
+    private int defaultMeetingDuration;
+    private boolean disableRecordingDefault;
+    private boolean autoStartRecording;
+    private boolean allowStartStopRecording;
+
+    private String defaultConfigXML = null;
+
+    private String substituteKeywords(String message, String dialNumber, String telVoice, String meetingName) {
+        String welcomeMessage = message;
+
+        String DIAL_NUM = "%%DIALNUM%%";
+        String CONF_NUM = "%%CONFNUM%%";
+        String CONF_NAME = "%%CONFNAME%%";
+        ArrayList<String> keywordList = new ArrayList<String>();
+        keywordList.add(DIAL_NUM);keywordList.add(CONF_NUM);keywordList.add(CONF_NAME);
+
+        Iterator<String> itr = keywordList.iterator();
+        while(itr.hasNext()) {
+            String keyword = (String) itr.next();
+            if (keyword.equals(DIAL_NUM)) {
+                welcomeMessage = welcomeMessage.replaceAll(DIAL_NUM, dialNumber);
+            } else if (keyword.equals(CONF_NUM)) {
+                welcomeMessage = welcomeMessage.replaceAll(CONF_NUM, telVoice);
+            } else if (keyword.equals(CONF_NAME)) {
+                welcomeMessage = welcomeMessage.replaceAll(CONF_NAME, meetingName);
+            }
+        }
+        return  welcomeMessage;
+    }
+
+    public void processRequiredCreateParams(Map<String, String> params, ApiErrors errors) {
+        // Do we have a checksum? If not, complain.
+        if (StringUtils.isEmpty(params.get("checksum"))) {
+            errors.missingParamError("checksum");
+        }
+
+        // Do we have a meeting id? If not, complain.
+        if(!StringUtils.isEmpty(params.get("meetingID"))) {
+            if (StringUtils.isEmpty(StringUtils.strip(params.get("meetingID")))) {
+                errors.missingParamError("meetingID");
+            }
+        } else {
+            errors.missingParamError("meetingID");
+        }
+    }
+
+	public void updateMeeting(Map<String, Object> updateParams, Meeting existing) {
+		// TODO: Assign new values to meeting.
+/*	    
+	    String meetingName = (String) updateParams.get("name");
+	    if (meetingName != null) {
+	    	existing.setM("name", meetingName);
+	    }
+	    	    
+	    String viewerPass = params.get("attendeePW");
+	    if (! StringUtils.isEmpty(viewerPass) ) {
+	    	newParams.put("attendeePW", viewerPass);
+	    }
+	    
+	    String modPass = params.get("moderatorPW"); 
+	    if (! StringUtils.isEmpty(modPass) ) {
+	    	newParams.put("moderatorPW", modPass);
+	    }
+	    
+	    String telVoice = params.get("voiceBridge");
+	    if (! StringUtils.isEmpty(telVoice) ) {
+	    	newParams.put("voiceBridge", telVoice);
+	    }	    
+	    
+	    String webVoice = params.get("webVoice");
+	    if (! StringUtils.isEmpty(webVoice)) {
+	    	newParams.put("webVoice", webVoice);
+	    }
+	    
+	    String dialNumber = params.get("dialNumber");
+	    if (! StringUtils.isEmpty(dialNumber)) {
+	    	newParams.put("dialNumber", dialNumber);
+	    }	    
+	    
+	    String logoutUrl = params.get("logoutURL"); 
+	    if (! StringUtils.isEmpty(logoutUrl)) {
+	    	newParams.put("logoutURL", logoutUrl);
+	    }	
+	    
+	    String record = params.get("record");
+	    if (! StringUtils.isEmpty(record)) {
+	    	newParams.put("record", record);
+	    }	
+	    
+	    String maxUsers = params.get("maxParticipants");
+	    if (! StringUtils.isEmpty(maxUsers)) {
+	    	newParams.put("maxParticipants", maxUsers);
+	    }	
+	    
+	    String meetingDuration = params.get("duration");
+	    if (! StringUtils.isEmpty(meetingDuration)) {
+	    	newParams.put("duration", meetingDuration);
+	    }
+	    
+	    String welcomeMessage = params.get("welcome");
+	    if (! StringUtils.isEmpty(welcomeMessage)) {
+	    	newParams.put("welcome", welcomeMessage);
+	    }
+	    	    	    
+	    // Collect metadata for this meeting that the third-party app wants to store if meeting is recorded.
+	    Map<String, String> meetingInfo = new HashMap<String, String>();
+	    for (String key: params.keySet()) {
+	    	if (key.contains("meta")){
+	    		String[] meta = key.split("_");
+			    if(meta.length == 2){
+			    	meetingInfo.put(meta[1], params.get(key));
+			    }
+			}   
+	    }
+
+	    if (! meetingInfo.isEmpty()) {
+	    	newParams.put("metadata", meetingInfo);
+	    }		
+*/
+	}
+	
+	public Map<String, Object> processUpdateCreateParams(Map<String, String> params) {
+		Map<String, Object> newParams = new HashMap<String, Object>();
+		    
+	    // Do we have a meeting name? If not, complain.
+	    String meetingName = params.get("name");
+	    if (! StringUtils.isEmpty(meetingName) ) {
+	    	newParams.put("name", meetingName);
+	    }
+	    	    
+	    String viewerPass = params.get("attendeePW");
+	    if (! StringUtils.isEmpty(viewerPass) ) {
+	    	newParams.put("attendeePW", viewerPass);
+	    }
+	    
+	    String modPass = params.get("moderatorPW"); 
+	    if (! StringUtils.isEmpty(modPass) ) {
+	    	newParams.put("moderatorPW", modPass);
+	    }
+	    
+	    String telVoice = params.get("voiceBridge");
+	    if (! StringUtils.isEmpty(telVoice) ) {
+	    	newParams.put("voiceBridge", telVoice);
+	    }	    
+	    
+	    String webVoice = params.get("webVoice");
+	    if (! StringUtils.isEmpty(webVoice)) {
+	    	newParams.put("webVoice", webVoice);
+	    }
+	    
+	    String dialNumber = params.get("dialNumber");
+	    if (! StringUtils.isEmpty(dialNumber)) {
+	    	newParams.put("dialNumber", dialNumber);
+	    }	    
+	    
+	    String logoutUrl = params.get("logoutURL"); 
+	    if (! StringUtils.isEmpty(logoutUrl)) {
+	    	newParams.put("logoutURL", logoutUrl);
+	    }	
+	    
+	    String record = params.get("record");
+	    if (! StringUtils.isEmpty(record)) {
+	    	newParams.put("record", record);
+	    }	
+	    
+	    String maxUsers = params.get("maxParticipants");
+	    if (! StringUtils.isEmpty(maxUsers)) {
+	    	newParams.put("maxParticipants", maxUsers);
+	    }	
+	    
+	    String meetingDuration = params.get("duration");
+	    if (! StringUtils.isEmpty(meetingDuration)) {
+	    	newParams.put("duration", meetingDuration);
+	    }
+	    
+	    String welcomeMessage = params.get("welcome");
+	    if (! StringUtils.isEmpty(welcomeMessage)) {
+	    	newParams.put("welcome", welcomeMessage);
+	    }
+	    	    	    
+	    // Collect metadata for this meeting that the third-party app wants to store if meeting is recorded.
+	    Map<String, String> meetingInfo = new HashMap<String, String>();
+	    for (String key: params.keySet()) {
+	    	if (key.contains("meta")){
+	    		String[] meta = key.split("_");
+			    if(meta.length == 2){
+			    	meetingInfo.put(meta[1], params.get(key));
+			    }
+			}   
+	    }
+
+	    if (! meetingInfo.isEmpty()) {
+	    	newParams.put("metadata", meetingInfo);
+	    }
+	    
+	    return newParams;
+	}
+	
+	private static final Pattern META_VAR_PATTERN = Pattern.compile("meta_[a-zA-Z][a-zA-Z0-9-]*$");	
+	public static Boolean isMetaValid(String param) {
+		Matcher metaMatcher = META_VAR_PATTERN.matcher(param);
+    if (metaMatcher.matches()) {
+    	return true;
+    }	
+		return false;
+	}
+	
+	public static String removeMetaString(String param) {
+		return StringUtils.removeStart(param, "meta_");
+	}
+	
+	public static Map<String, String> processMetaParam(Map<String, String> params) {
+    Map<String, String> metas = new HashMap<String, String>();
+    for (String key: params.keySet()) {
+    	if (isMetaValid(key)){
+    		// Need to lowercase to maintain backward compatibility with 0.81
+    		String metaName = removeMetaString(key).toLowerCase();
+    		metas.put(metaName, params.get(key));
+		  }   
+    }
+    
+    return metas;
+	}
+	
+	public Meeting processCreateParams(Map<String, String> params) {
+	    String meetingName = params.get("name");
+	    if(meetingName == null){
+	    	meetingName = "";
+	    }
+	    String externalMeetingId = params.get("meetingID");
+	    
+	    String viewerPass = processPassword(params.get("attendeePW"));
+	    String modPass = processPassword(params.get("moderatorPW")); 
+	    
+	    // Get the digits for voice conference for users joining through the phone.
+	    // If none is provided, generate one.
+	    String telVoice = processTelVoice(params.get("voiceBridge"));
+	    
+	    // Get the voice conference digits/chars for users joing through VOIP on the client.
+	    // If none is provided, make it the same as the telVoice. If one has been provided,
+	    // we expect that the users will be joined in the same voice conference.
+	    String webVoice = params.get("webVoice");
+	    if (StringUtils.isEmpty(webVoice)) {
+	      webVoice = telVoice;
+	    }
+	    
+	    // Get all the other relevant parameters and generate defaults if none has been provided.
+	    String dialNumber = processDialNumber(params.get("dialNumber"));
+	    String logoutUrl = processLogoutUrl(params.get("logoutURL")); 
+	    boolean record = processRecordMeeting(params.get("record"));
+	    int maxUsers = processMaxUser(params.get("maxParticipants"));
+	    int meetingDuration = processMeetingDuration(params.get("duration"));
+	    String welcomeMessage = processWelcomeMessage(params.get("welcome"));
+	    welcomeMessage = substituteKeywords(welcomeMessage, dialNumber, telVoice, meetingName);
+	   
+	    // set is breakout room property
+	    boolean isBreakout = false;
+	    if (! StringUtils.isEmpty(params.get("isBreakout"))) {
+	      isBreakout = new Boolean(params.get("isBreakout"));
+	    }
+	      
+	    String internalMeetingId = convertToInternalMeetingId(externalMeetingId);
+	    
+	    // Check if this is a test meeting. NOTE: This should not belong here. Extract this out.				
+	    if (isTestMeeting(telVoice)) {
+	      internalMeetingId = getIntMeetingIdForTestMeeting(telVoice);
+	    }
+	   
+	    boolean autoStartRec = autoStartRecording;
+	    if (!StringUtils.isEmpty(params.get("autoStartRecording"))) {
+				try {
+					autoStartRec = Boolean.parseBoolean(params.get("autoStartRecording"));
+				} catch(Exception ex){ 
+					log.warn("Invalid param [autoStartRecording] for meeting=[" + internalMeetingId + "]");
+				}
+	    }
+
+	    boolean allowStartStoptRec = allowStartStopRecording;
+	    if (!StringUtils.isEmpty(params.get("allowStartStopRecording"))) {
+				try {
+					allowStartStoptRec = Boolean.parseBoolean(params.get("allowStartStopRecording"));
+				} catch(Exception ex){ 
+					log.warn("Invalid param [allowStartStopRecording] for meeting=[" + internalMeetingId + "]");
+				}
+	    }
+	    
+	    // Collect metadata for this meeting that the third-party app wants to store if meeting is recorded.
+	    Map<String, String> meetingInfo = new HashMap<String, String>();
+	    meetingInfo = processMetaParam(params);
+	    	    
+	    // Create a unique internal id by appending the current time. This way, the 3rd-party
+	    // app can reuse the external meeting id.
+	    long createTime = System.currentTimeMillis();
+	    internalMeetingId = internalMeetingId + '-' + new Long(createTime).toString();
+	    
+      // If this create meeting request is for a breakout room, we just used
+      // the passed in breakoutId as the internal meetingId so we can correlate
+      // the breakout meeting with it's parent meeting.
+      if (isBreakout) {
+        internalMeetingId = params.get("breakoutId");
+      }
+      
+	    // Create the meeting with all passed in parameters.
+	    Meeting meeting = new Meeting.Builder(externalMeetingId, internalMeetingId, createTime)
+	        .withName(meetingName).withMaxUsers(maxUsers).withModeratorPass(modPass)
+	        .withViewerPass(viewerPass).withRecording(record).withDuration(meetingDuration)
+	        .withLogoutUrl(logoutUrl).withTelVoice(telVoice).withWebVoice(webVoice).withDialNumber(dialNumber)
+	        .withDefaultAvatarURL(defaultAvatarURL).withAutoStartRecording(autoStartRec).withAllowStartStopRecording(allowStartStoptRec)
+	        .withMetadata(meetingInfo).withWelcomeMessage(welcomeMessage).isBreakout(isBreakout).build();
+	    
+	    String configXML = getDefaultConfigXML();
+	    meeting.storeConfig(true, configXML);
+	     
+	    if (! StringUtils.isEmpty(params.get("moderatorOnlyMessage"))) {
+	      String moderatorOnlyMessage = params.get("moderatorOnlyMessage");
+	      meeting.setModeratorOnlyMessage(moderatorOnlyMessage);
+	    }
+	    
+	    return meeting;
+	}
+	
+	public String getApiVersion() {
+		return apiVersion;
+	}
+	
+	public boolean isServiceEnabled() {
+		return serviceEnabled;
+	}
+	
+	public String getDefaultClientUrl() {
+		return defaultClientUrl;
+	}
+	
+	public String getDefaultConfigXML() {
+		if (defaultConfigXML == null) {
+			defaultConfigXML = getConfig(defaultConfigURL);
+		}
+		
+		return defaultConfigXML;
+	}
+	
+	private String getConfig(String url) {
+		HttpClient client = new HttpClient();
+		GetMethod get = new GetMethod(url);
+		String configXML = "";
+		try {
+			int status = client.executeMethod(get);
+			if (status == 200) {
+				configXML = get.getResponseBodyAsString();
+			} else {
+				return null;
+			}
+			
+		} catch (HttpException e) {
+			return null;
+		} catch (IOException e) {
+			return null;
+		} finally {
+			get.releaseConnection();
+		}
+		  		  
+		return configXML;
+	  }
+	
+	public String getDefaultConfigURL() {
+		return defaultConfigURL;
+	}
+	
+	public String getDefaultLogoutUrl() {
+		 if ((StringUtils.isEmpty(defaultLogoutUrl)) || defaultLogoutUrl.equalsIgnoreCase("default")) {          
+     		return defaultServerUrl;
+     	} else {
+     		return defaultLogoutUrl;
+     	}
+	}
+	
+	public String processWelcomeMessage(String message) {
+		String welcomeMessage = message;
+		if (StringUtils.isEmpty(message)) {
+			welcomeMessage = defaultWelcomeMessage;
+		}
+		if( !StringUtils.isEmpty(defaultWelcomeMessageFooter) )
+		    welcomeMessage += "<br><br>" + defaultWelcomeMessageFooter;
+		return welcomeMessage;
+	}
+
+	public String convertToInternalMeetingId(String extMeetingId) {
+		return DigestUtils.shaHex(extMeetingId);
+	}
+	
+	public String processPassword(String pass) {
+		return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
+	}
+
+	public boolean hasChecksumAndQueryString(String checksum, String queryString) {
+		return (! StringUtils.isEmpty(checksum) && StringUtils.isEmpty(queryString));
+	}
+		
+	public String processTelVoice(String telNum) {
+		return StringUtils.isEmpty(telNum) ? RandomStringUtils.randomNumeric(defaultNumDigitsForTelVoice) : telNum;
+	}
+		
+	public String processDialNumber(String dial) {
+		return StringUtils.isEmpty(dial) ? defaultDialAccessNumber : dial;	
+	}
+	
+	public String processLogoutUrl(String logoutUrl) {
+		if (StringUtils.isEmpty(logoutUrl)) {
+	        if ((StringUtils.isEmpty(defaultLogoutUrl)) || defaultLogoutUrl.equalsIgnoreCase("default")) {          
+        		return defaultServerUrl;
+        	} else {
+        		return defaultLogoutUrl;
+        	}	
+		}
+		
+		return logoutUrl;
+	}
+	
+	public boolean processRecordMeeting(String record) {
+		// The administrator has turned off recording for all meetings.
+		if (disableRecordingDefault) {
+			log.info("Recording is turned OFF by default.");
+			return false;
+		}
+		
+		boolean rec = false;			
+		if(! StringUtils.isEmpty(record)){
+			try {
+				rec = Boolean.parseBoolean(record);
+			} catch(Exception ex){ 
+				rec = false;
+			}
+		}
+		
+		return rec;
+	}
+		
+	public int processMaxUser(String maxUsers) {
+		int mUsers = -1;
+		
+		try {
+			mUsers = Integer.parseInt(maxUsers);
+		} catch(Exception ex) { 
+			mUsers = defaultMaxUsers;
+		}		
+		
+		return mUsers;
+	}	
+
+  public int processMeetingDuration(String duration) {
+    int mDuration = -1;
+    
+    try {
+      mDuration = Integer.parseInt(duration);
+    } catch(Exception ex) { 
+      mDuration = defaultMeetingDuration;
+    }   
+    
+    return mDuration;
+  } 
+  	
+	public boolean isTestMeeting(String telVoice) {	
+		return ((! StringUtils.isEmpty(telVoice)) && 
+				(! StringUtils.isEmpty(testVoiceBridge)) && 
+				(telVoice == testVoiceBridge));	
+	}
+		
+	public String getIntMeetingIdForTestMeeting(String telVoice) {		
+		if ((testVoiceBridge != null) && (telVoice == testVoiceBridge)) {
+			if (StringUtils.isEmpty(testConferenceMock)) 
+				return testConferenceMock;
+		} 
+		
+		return "";	
+	}
+	
+	public boolean isConfigXMLChecksumSame(String meetingID, String configXML, String checksum) {
+		if (StringUtils.isEmpty(securitySalt)) {
+			log.warn("Security is disabled in this service. Make sure this is intentional.");
+			return true;
+		}
+        
+		String cs = DigestUtils.shaHex(meetingID + configXML + securitySalt);
+
+		if (cs == null || cs.equals(checksum) == false) {
+			log.info("checksumError: configXML checksum. our: [{}], client: [{}]", cs, checksum);
+			return false;
+		}
+		return true;
+	}
+	
+	public boolean isChecksumSame(String apiCall, String checksum, String queryString) {
+		if (StringUtils.isEmpty(securitySalt)) {
+			log.warn("Security is disabled in this service. Make sure this is intentional.");
+			return true;
+		}
+
+		if( queryString == null ) {
+		    queryString = "";
+		} else {
+		    // handle either checksum as first or middle / end parameter
+		    // TODO: this is hackish - should be done better
+		    queryString = queryString.replace("&checksum=" + checksum, "");
+		    queryString = queryString.replace("checksum=" + checksum + "&", "");
+		    queryString = queryString.replace("checksum=" + checksum, "");
+		}
+
+		String cs = DigestUtils.shaHex(apiCall + queryString + securitySalt);
+
+		if (cs == null || cs.equals(checksum) == false) {
+			log.info("query string after checksum removed: [{}]", queryString);
+			log.info("checksumError: query string checksum failed. our: [{}], client: [{}]", cs, checksum);
+			return false;
+		}
+
+		return true; 
+	}
+	
+	public boolean isPostChecksumSame(String apiCall, HashMap<String, String[]> params) {
+		if (StringUtils.isEmpty(securitySalt)) {
+			log.warn("Security is disabled in this service. Make sure this is intentional.");
+			return true;
+		}
+
+		StringBuffer csbuf = new StringBuffer();
+		csbuf.append(apiCall);
+ 
+		SortedSet<String> keys = new TreeSet<String>(params.keySet());
+ 
+		boolean first = true;
+		String checksum = null;
+		for (String key: keys) {
+			if (key.equals("checksum")) {
+				// Don't include the "checksum" parameter in the checksum
+				checksum = params.get(key)[0];
+				continue;
+			}
+ 
+			for (String value: params.get(key)) {
+				if (first) {
+					first = false;
+				} else {
+					csbuf.append("&");
+				}
+				csbuf.append(key);
+				csbuf.append("=");
+				String encResult;
+
+				encResult = value;
+				
+/*****
+ * Seems like Grails 2.3.6 decodes the string. So we need to re-encode it.
+ * We'll remove this later. richard (aug 5, 2014)						
+*/				try {       
+					// we need to re-encode the values because Grails unencoded it
+					// when it received the 'POST'ed data. Might not need to do in a GET request.
+					encResult = URLEncoder.encode(value, "UTF-8");  
+				} catch (UnsupportedEncodingException e) {       
+					encResult = value;     
+				} 					
+
+				csbuf.append(encResult);
+			}
+		}
+		csbuf.append(securitySalt);
+
+		String baseString = csbuf.toString();				
+		String cs = DigestUtils.shaHex(baseString);
+		
+		if (cs == null || cs.equals(checksum) == false) {
+			log.info("POST basestring = [" + baseString + "]");
+			log.info("checksumError: failed checksum. our checksum: [{}], client: [{}]", cs, checksum);
+			return false;
+		}
+
+		return true;
+	}
+
+	/*************************************************
+	 * Setters
+	 ************************************************/
+	
+	public void setApiVersion(String apiVersion) {
+		this.apiVersion = apiVersion;
+	}
+
+	public void setServiceEnabled(boolean e) {
+		serviceEnabled = e;
+	}
+	
+	public void setSecuritySalt(String securitySalt) {
+		this.securitySalt = securitySalt;
+	}
+
+	public void setDefaultMaxUsers(int defaultMaxUsers) {
+		this.defaultMaxUsers = defaultMaxUsers;
+	}
+
+	public void setDefaultWelcomeMessage(String defaultWelcomeMessage) {
+		this.defaultWelcomeMessage = defaultWelcomeMessage;
+	}
+	
+	public void setDefaultWelcomeMessageFooter(String defaultWelcomeMessageFooter) {
+	    this.defaultWelcomeMessageFooter = defaultWelcomeMessageFooter;
+	}
+
+	public void setDefaultDialAccessNumber(String defaultDialAccessNumber) {
+		this.defaultDialAccessNumber = defaultDialAccessNumber;
+	}
+
+	public void setTestVoiceBridge(String testVoiceBridge) {
+		this.testVoiceBridge = testVoiceBridge;
+	}
+
+	public void setTestConferenceMock(String testConferenceMock) {
+		this.testConferenceMock = testConferenceMock;
+	}
+
+	public void setDefaultLogoutUrl(String defaultLogoutUrl) {
+		this.defaultLogoutUrl = defaultLogoutUrl;
+	}
+
+	public void setDefaultConfigURL(String defaultConfigUrl) {
+		this.defaultConfigURL = defaultConfigUrl;
+	}
+	
+	public void setDefaultServerUrl(String defaultServerUrl) {
+		this.defaultServerUrl = defaultServerUrl;
+	}
+
+	public void setDefaultNumDigitsForTelVoice(int defaultNumDigitsForTelVoice) {
+		this.defaultNumDigitsForTelVoice = defaultNumDigitsForTelVoice;
+	}
+
+	public void setDefaultClientUrl(String defaultClientUrl) {
+		this.defaultClientUrl = defaultClientUrl;
+	}
+
+	public void setDefaultMeetingDuration(int defaultMeetingDuration) {
+		this.defaultMeetingDuration = defaultMeetingDuration;
+	}
+
+	public void setDisableRecordingDefault(boolean disabled) {
+		this.disableRecordingDefault = disabled;
+	}
+	
+	public void setAutoStartRecording(boolean start) {
+		this.autoStartRecording = start;
+	}
+
+	public void setAllowStartStopRecording(boolean allowStartStopRecording) {
+		this.allowStartStopRecording = allowStartStopRecording;
+	}
+	
+	public void setdefaultAvatarURL(String url) {
+		this.defaultAvatarURL = url;
+	}
+
+	public ArrayList<String> decodeIds(String encodeid) {
+		ArrayList<String> ids=new ArrayList<String>();
+		try {
+			ids.addAll(Arrays.asList(URLDecoder.decode(encodeid,"UTF-8").split(URLDECODER_SEPARATOR)));
+		} catch (UnsupportedEncodingException e) {
+			log.error("Couldn't decode the IDs");
+		}
+		
+		return ids;
+	}
+
+	public ArrayList<String> convertToInternalMeetingId(ArrayList<String> extMeetingIds) {
+		ArrayList<String> internalMeetingIds=new ArrayList<String>();
+		for(String extid : extMeetingIds){
+			internalMeetingIds.add(convertToInternalMeetingId(extid));
+		}
+		return internalMeetingIds;
+	}
+	
+	public Map<String,String> getUserCustomData(Map<String,String> params) {
+		Map<String,String> resp = new HashMap<String, String>();
+
+		for (String key: params.keySet()) {
+	    	if (key.contains("userdata")&&key.indexOf("userdata")==0){
+	    		String[] userdata = key.split("-");
+			    if(userdata.length == 2){
+			    	log.debug("Got user custom data {} = {}", key, params.get(key));
+			    	resp.put(userdata[1], params.get(key));
+			    }
+			}   
+	    }
+
+		return resp;
+	}
+
+	public Map<String, Map<String, Object>> decodeFilters(String encodedFilters) {
+        Map<String, Map<String, Object>> filters = new LinkedHashMap<String, Map<String, Object>>();
+
+        try {
+            String[] sFilters = encodedFilters.split(URLDECODER_SEPARATOR);
+            for( String sFilter: sFilters) {
+                String[] filterElements = sFilter.split(FILTERDECODER_SEPARATOR_ELEMENTS, 3);
+                Map<String, Object> filter = new LinkedHashMap<String, Object>();
+                filter.put("op", filterElements[1]);
+                String[] fValues = filterElements[2].split(FILTERDECODER_SEPARATOR_OPERATORS);
+                filter.put("values", fValues );
+                filters.put(filterElements[0], filter);
+            }
+        } catch (Exception e) {
+            log.error("Couldn't decode the filters");
+        }
+
+        return filters;
+    }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingService.java
new file mode 100755
index 0000000000..55c8c3cda6
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingService.java
@@ -0,0 +1,404 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bigbluebutton.api.domain.Recording;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RecordingService {
+    private static Logger log = LoggerFactory.getLogger(RecordingService.class);
+
+    private String processDir = "/var/bigbluebutton/recording/process";
+    private String publishedDir = "/var/bigbluebutton/published";
+    private String unpublishedDir = "/var/bigbluebutton/unpublished";
+    private String deletedDir = "/var/bigbluebutton/deleted";
+    private RecordingServiceHelper recordingServiceHelper;
+    private String recordStatusDir;
+
+    public void startIngestAndProcessing(String meetingId) {
+        String done = recordStatusDir + "/" + meetingId + ".done";
+
+        File doneFile = new File(done);
+        if (!doneFile.exists()) {
+            try {
+                doneFile.createNewFile();
+                if (!doneFile.exists())
+                    log.error("Failed to create " + done + " file.");
+            } catch (IOException e) {
+                log.error("Failed to create " + done + " file.");
+            }
+        } else {
+            log.error(done + " file already exists.");
+        }
+    }
+
+    public List<Recording> getRecordings(List<String> recordIDs, List<String> states) {
+        List<Recording> recs = new ArrayList<Recording>();
+
+        Map<String, List<File>> allDirectories = getAllDirectories(states);
+        if (recordIDs.isEmpty()) {
+            for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
+                recordIDs.addAll(getAllRecordingIds(entry.getValue()));
+            }
+        }
+
+        for (String recordID : recordIDs) {
+            for (Map.Entry<String, List<File>> entry : allDirectories.entrySet()) {
+                List<Recording> _recs = getRecordingsForPath(recordID, entry.getValue());
+                recs.addAll(_recs);
+            }
+        }
+
+        return recs;
+    }
+
+    public boolean recordingMatchesMetadata(Recording recording, Map<String, String> metadataFilters) {
+        boolean matchesMetadata = true;
+        for (Map.Entry<String, String> filter : metadataFilters.entrySet()) {
+            String metadataValue = recording.getMetadata().get(filter.getKey());
+            if ( metadataValue == null ) {
+                // The recording doesn't have metadata specified
+                matchesMetadata = false;
+            } else {
+                String filterValue = filter.getValue();
+                if( filterValue.charAt(0) == '%' && filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.contains(filterValue.substring(1, filterValue.length()-1)) ){
+                    // Filter value embraced by two wild cards
+                    // AND the filter value is part of the metadata value
+                } else if( filterValue.charAt(0) == '%' && metadataValue.endsWith(filterValue.substring(1, filterValue.length())) ) {
+                    // Filter value starts with a wild cards
+                    // AND the filter value ends with the metadata value
+                } else if( filterValue.charAt(filterValue.length()-1) == '%' && metadataValue.startsWith(filterValue.substring(0, filterValue.length()-1)) ) {
+                    // Filter value ends with a wild cards
+                    // AND the filter value starts with the metadata value
+                } else if( metadataValue.equals(filterValue) ) {
+                    // Filter value doesnt have wildcards
+                    // AND the filter value is the same as metadata value
+                } else {
+                    matchesMetadata = false;
+                }
+            }
+        }
+        return matchesMetadata;
+    }
+
+    public Map<String, Recording> filterRecordingsByMetadata(Map<String, Recording> recordings, Map<String, String> metadataFilters) {
+        Map<String, Recording> resultRecordings = new HashMap<String, Recording>();
+        for (Map.Entry<String, Recording> entry : recordings.entrySet()) {
+            if (recordingMatchesMetadata(entry.getValue(), metadataFilters))
+                resultRecordings.put(entry.getKey(), entry.getValue());
+        }
+        return resultRecordings;
+    }
+
+    public boolean existAnyRecording(List<String> idList) {
+        List<String> publishList = getAllRecordingIds(publishedDir);
+        List<String> unpublishList = getAllRecordingIds(unpublishedDir);
+
+        for (String id : idList) {
+            if (publishList.contains(id) || unpublishList.contains(id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<String> getAllRecordingIds(String path) {
+        String[] format = getPlaybackFormats(path);
+
+        return getAllRecordingIds(path, format);
+    }
+
+    private List<String> getAllRecordingIds(String path, String[] format) {
+        List<String> ids = new ArrayList<String>();
+
+        for (int i = 0; i < format.length; i++) {
+            List<File> recordings = getDirectories(path + File.separatorChar + format[i]);
+            for (int f = 0; f < recordings.size(); f++) {
+                if (!ids.contains(recordings.get(f).getName()))
+                    ids.add(recordings.get(f).getName());
+            }
+        }
+
+        return ids;
+    }
+
+    private Set<String> getAllRecordingIds(List<File> recs) {
+        Set<String> ids = new HashSet<String>();
+
+        Iterator<File> iterator = recs.iterator();
+        while (iterator.hasNext()) {
+            ids.add(iterator.next().getName());
+        }
+
+        return ids;
+    }
+
+    private List<Recording> getRecordingsForPath(String id, List<File> recordings) {
+        List<Recording> recs = new ArrayList<Recording>();
+
+        Iterator<File> iterator = recordings.iterator();
+        while (iterator.hasNext()) {
+            File recording = iterator.next();
+            if (recording.getName().startsWith(id)) {
+                Recording r = getRecordingInfo(recording);
+                if (r != null)
+                    recs.add(r);
+            }
+        }
+        return recs;
+    }
+
+    private Recording getRecordingInfo(File dir) {
+        Recording rec = recordingServiceHelper.getRecordingInfo(dir);
+        return rec;
+    }
+
+    private void deleteRecording(String id, String path) {
+        String[] format = getPlaybackFormats(path);
+        for (int i = 0; i < format.length; i++) {
+            List<File> recordings = getDirectories(path + File.separatorChar + format[i]);
+            for (int f = 0; f < recordings.size(); f++) {
+                if (recordings.get(f).getName().equals(id)) {
+                    deleteDirectory(recordings.get(f));
+                    createDirectory(recordings.get(f));
+                }
+            }
+        }
+    }
+
+    private void createDirectory(File directory) {
+        if (!directory.exists())
+            directory.mkdirs();
+    }
+
+    private void deleteDirectory(File directory) {
+        /**
+         * Go through each directory and check if it's not empty. We need to
+         * delete files inside a directory before a directory can be deleted.
+         **/
+        File[] files = directory.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].isDirectory()) {
+                deleteDirectory(files[i]);
+            } else {
+                files[i].delete();
+            }
+        }
+        // Now that the directory is empty. Delete it.
+        directory.delete();
+    }
+
+    private List<File> getDirectories(String path) {
+        List<File> files = new ArrayList<File>();
+        try {
+            DirectoryStream<Path> stream = Files.newDirectoryStream(FileSystems.getDefault().getPath(path));
+            Iterator<Path> iter = stream.iterator();
+            while (iter.hasNext()) {
+                Path next = iter.next();
+                files.add(next.toFile());
+            }
+            stream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return files;
+    }
+
+    private String[] getPlaybackFormats(String path) {
+        List<File> dirs = getDirectories(path);
+        String[] formats = new String[dirs.size()];
+
+        for (int i = 0; i < dirs.size(); i++) {
+            formats[i] = dirs.get(i).getName();
+        }
+        return formats;
+    }
+
+    public void setRecordingStatusDir(String dir) {
+        recordStatusDir = dir;
+    }
+
+    public void setUnpublishedDir(String dir) {
+        unpublishedDir = dir;
+    }
+
+    public void setPublishedDir(String dir) {
+        publishedDir = dir;
+    }
+
+    public void setRecordingServiceHelper(RecordingServiceHelper r) {
+        recordingServiceHelper = r;
+    }
+
+    private boolean shouldIncludeState(List<String> states, String type) {
+        boolean r = false;
+
+        if (!states.isEmpty()) {
+            if (states.contains("any")) {
+                r = true;
+            } else {
+                if (type.equals(Recording.STATE_PUBLISHED) && states.contains(Recording.STATE_PUBLISHED)) {
+                    r = true;
+                } else if (type.equals(Recording.STATE_UNPUBLISHED) && states.contains(Recording.STATE_UNPUBLISHED)) {
+                    r = true;
+                } else if (type.equals(Recording.STATE_DELETED) && states.contains(Recording.STATE_DELETED)) {
+                    r = true;
+                } else if (type.equals(Recording.STATE_PROCESSING) && states.contains(Recording.STATE_PROCESSING)) {
+                    r = true;
+                } else if (type.equals(Recording.STATE_PROCESSED) && states.contains(Recording.STATE_PROCESSED)) {
+                    r = true;
+                }
+            }
+
+        } else {
+            if (type.equals(Recording.STATE_PUBLISHED) || type.equals(Recording.STATE_UNPUBLISHED)) {
+                r = true;
+            }
+        }
+
+        return r;
+    }
+
+    public void changeState(String recordingId, String state) {
+        if (state.equals(Recording.STATE_PUBLISHED)) {
+            // It can only be published if it is unpublished
+            changeState(unpublishedDir, recordingId, state);
+        } else if (state.equals(Recording.STATE_UNPUBLISHED)) {
+            // It can only be unpublished if it is published
+            changeState(publishedDir, recordingId, state);
+        } else if (state.equals(Recording.STATE_DELETED)) {
+            // It can be deleted from any state
+            changeState(publishedDir, recordingId, state);
+            changeState(unpublishedDir, recordingId, state);
+        }
+    }
+
+    private void changeState(String path, String recordingId, String state) {
+        String[] format = getPlaybackFormats(path);
+        for (int i = 0; i < format.length; i++) {
+            List<File> recordings = getDirectories(path + File.separatorChar + format[i]);
+            for (int f = 0; f < recordings.size(); f++) {
+                if (recordings.get(f).getName().equalsIgnoreCase(recordingId)) {
+                    Recording r = getRecordingInfo(recordings.get(f));
+                    if (r != null) {
+                        File dest;
+                        if (state.equals(Recording.STATE_PUBLISHED)) {
+                            dest = new File(publishedDir + File.separatorChar + format[i]);
+                        } else if (state.equals(Recording.STATE_UNPUBLISHED)) {
+                            dest = new File(unpublishedDir + File.separatorChar + format[i]);
+                        } else if (state.equals(Recording.STATE_DELETED)) {
+                            dest = new File(deletedDir + File.separatorChar + format[i]);
+                        } else {
+                            log.debug(String.format("State: %s, is not supported", state));
+                            return;
+                        }
+                        if (!dest.exists())
+                            dest.mkdirs();
+                        boolean moved = recordings.get(f).renameTo(new File(dest, recordings.get(f).getName()));
+                        if (moved) {
+                            log.debug("Recording successfully moved!");
+                            r.setState(state);
+                            r.setPublished(state.equals(Recording.STATE_PUBLISHED));
+                            if (state.equals(Recording.STATE_DELETED)) {
+                                r.setPlaybackFormat(null);
+                                deleteRecording(recordingId, deletedDir);
+                            }
+                            recordingServiceHelper.writeRecordingInfo(dest.getAbsolutePath() + File.separatorChar + recordings.get(f).getName(), r);
+                            log.debug(String.format("Recording successfully %s!", state));
+                        } else {
+                            log.debug("Recording was not moved");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private List<File> getAllDirectories(String state) {
+        List<File> allDirectories = new ArrayList<File>();
+
+        String dir = null;
+        if( state.equals(Recording.STATE_PUBLISHED) ) {
+            dir = publishedDir;
+        } else if ( state.equals(Recording.STATE_UNPUBLISHED) ){
+            dir = unpublishedDir;
+        } else if ( state.equals(Recording.STATE_DELETED) ){
+            dir = deletedDir;
+        } else if ( state.equals(Recording.STATE_PROCESSING) || state.equals(Recording.STATE_PROCESSED) ){
+            dir = processDir;
+        }
+
+        if ( dir != null ) {
+            String[] formats = getPlaybackFormats(dir);
+            for (int i = 0; i < formats.length; ++i) {
+                allDirectories.addAll(getDirectories(dir + File.separatorChar + formats[i]));
+            }
+        }
+
+        return allDirectories;
+    }
+
+    private Map<String, List<File>> getAllDirectories(List<String> states) {
+        Map<String, List<File>> allDirectories = new HashMap<String, List<File>>();
+
+        if ( shouldIncludeState(states, Recording.STATE_PUBLISHED) ) {
+            List<File> _allDirectories = getAllDirectories(Recording.STATE_PUBLISHED);
+            allDirectories.put(Recording.STATE_PUBLISHED, _allDirectories);
+        }
+
+        if ( shouldIncludeState(states, Recording.STATE_UNPUBLISHED) ) {
+            List<File> _allDirectories = getAllDirectories(Recording.STATE_UNPUBLISHED);
+            allDirectories.put(Recording.STATE_UNPUBLISHED, _allDirectories);
+        }
+
+        if ( shouldIncludeState(states, Recording.STATE_DELETED) ) {
+            List<File> _allDirectories = getAllDirectories(Recording.STATE_DELETED);
+            allDirectories.put(Recording.STATE_DELETED, _allDirectories);
+        }
+
+        if ( shouldIncludeState(states, Recording.STATE_PROCESSING) ) {
+            List<File> _allDirectories = getAllDirectories(Recording.STATE_PROCESSING);
+            allDirectories.put(Recording.STATE_PROCESSING, _allDirectories);
+        }
+
+        if ( shouldIncludeState(states, Recording.STATE_PROCESSED) ) {
+            List<File> _allDirectories = getAllDirectories(Recording.STATE_PROCESSED);
+            allDirectories.put(Recording.STATE_PROCESSED, _allDirectories);
+        }
+
+        return allDirectories;
+    }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingServiceHelper.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingServiceHelper.java
new file mode 100755
index 0000000000..ce9fa7164b
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/RecordingServiceHelper.java
@@ -0,0 +1,29 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api;
+
+import java.io.File;
+
+import org.bigbluebutton.api.domain.Recording;
+
+public interface RecordingServiceHelper {
+	public Recording getRecordingInfo(File dir);
+	public void writeRecordingInfo(String path, Recording info);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/Util.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/Util.java
new file mode 100755
index 0000000000..a8bbf7cff5
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/Util.java
@@ -0,0 +1,30 @@
+package org.bigbluebutton.api;
+
+import java.io.File;
+import org.apache.commons.codec.digest.DigestUtils;
+
+public final class Util {
+	
+	public static String generatePresentationId(String name) {
+		long timestamp = System.currentTimeMillis();		
+		return DigestUtils.shaHex(name) + "-" + timestamp;
+	}
+	
+	public static String getFilenameExt(String filename) {
+		return filename.substring(filename.lastIndexOf("."));
+	}
+	
+	public static String createNewFilename(String presId, String fileExt) {
+		return presId + fileExt;
+	}
+	
+	public static File createPresentationDirectory(String meetingId, String presentationDir, String presentationId) {
+		String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId;
+		String presPath = meetingPath + File.separatorChar + presentationId;
+		File dir = new File(presPath);
+		if (dir.mkdirs()) {
+			return dir;
+		}
+		return null;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Config.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Config.java
new file mode 100755
index 0000000000..03e172ffe0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Config.java
@@ -0,0 +1,14 @@
+package org.bigbluebutton.api.domain;
+
+public class Config {
+	
+	public final String token;
+	public final long createdOn;
+	public final String config;
+	
+	public Config(String token, long timestamp, String config) {
+		this.token = token;
+		this.createdOn = timestamp;
+		this.config = config;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Meeting.java
new file mode 100755
index 0000000000..34320d7ae6
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Meeting.java
@@ -0,0 +1,494 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.commons.lang.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Meeting {
+	private static Logger log = LoggerFactory.getLogger(Meeting.class);
+	
+	private static final long MILLIS_IN_A_MINUTE = 60000;
+	
+	private String name;
+	private String extMeetingId;
+	private String intMeetingId;	
+	private Integer duration = 0;	 
+	private long createdTime = 0;
+	private long startTime = 0;
+	private long endTime = 0;
+	private boolean forciblyEnded = false;
+	private String telVoice;
+	private String webVoice;
+	private String moderatorPass;
+	private String viewerPass;
+	private String welcomeMsg;
+	private String modOnlyMessage;
+	private String logoutUrl;
+	private int maxUsers;
+	private boolean record;
+	private boolean autoStartRecording = false;
+	private boolean allowStartStopRecording = false;
+	private String dialNumber;
+	private String defaultAvatarURL;
+	private String defaultConfigToken;
+	private boolean userHasJoined = false;
+	private Map<String, String> metadata;
+	private Map<String, Object> userCustomData;
+	private final ConcurrentMap<String, User> users;
+	private final ConcurrentMap<String, Long> registeredUsers;
+	private final ConcurrentMap<String, Config> configs;
+	private final Boolean isBreakout;
+	
+	private long lastUserLeftOn = 0;
+	
+	public Meeting(Builder builder) {
+		name = builder.name;
+		extMeetingId = builder.externalId;
+		intMeetingId = builder.internalId;
+		viewerPass = builder.viewerPass;
+		moderatorPass = builder.moderatorPass;
+		maxUsers = builder.maxUsers;
+		logoutUrl = builder.logoutUrl;
+		defaultAvatarURL = builder.defaultAvatarURL;
+		record = builder.record;
+		autoStartRecording = builder.autoStartRecording;
+		allowStartStopRecording = builder.allowStartStopRecording;
+   	duration = builder.duration;
+   	webVoice = builder.webVoice;
+   	telVoice = builder.telVoice;
+   	welcomeMsg = builder.welcomeMsg;
+   	dialNumber = builder.dialNumber;
+   	metadata = builder.metadata;
+   	createdTime = builder.createdTime;
+   	isBreakout = builder.isBreakout;
+   	
+   	userCustomData = new HashMap<String, Object>();
+
+		users = new ConcurrentHashMap<String, User>();
+		registeredUsers = new ConcurrentHashMap<String, Long>();
+		
+		configs = new ConcurrentHashMap<String, Config>();
+	}
+
+	public String storeConfig(boolean defaultConfig, String config) {
+		String token = RandomStringUtils.randomAlphanumeric(8);
+		while (configs.containsKey(token)) {
+			token = RandomStringUtils.randomAlphanumeric(8);
+		}
+		
+		configs.put(token, new Config(token, System.currentTimeMillis(), config));
+		
+		if (defaultConfig) {
+			defaultConfigToken = token;
+		}
+		
+		return token;
+	}
+	
+	public Config getDefaultConfig() {
+		if (defaultConfigToken != null) {
+			return getConfig(defaultConfigToken);
+		}
+		
+		return null;
+	}
+	
+	public Config getConfig(String token) {
+		return configs.get(token);
+	}
+	
+	public Config removeConfig(String token) {
+		return configs.remove(token);
+	}
+
+	public Map<String, String> getMetadata() {
+		return metadata;
+	}
+
+	public Collection<User> getUsers() {
+		return users.isEmpty() ? Collections.<User>emptySet() : Collections.unmodifiableCollection(users.values());
+	}
+
+	public ConcurrentMap<String, User> getUsersMap() {
+	    return users;
+	}
+
+	public long getStartTime() {
+		return startTime;
+	}
+	
+	public void setStartTime(long t) {
+		startTime = t;
+	}
+	
+	public long getCreateTime() {
+		return createdTime;
+	}
+	
+	public Integer getDuration() {
+		return duration;
+	}
+	
+	public long getEndTime() {
+		return endTime;
+	}
+	
+	public void setModeratorOnlyMessage(String msg) {
+		modOnlyMessage = msg;
+	}
+	
+	public String getModeratorOnlyMessage() {
+		return modOnlyMessage;
+	}
+	
+	public void setEndTime(long t) {
+		endTime = t;
+	}
+	
+	public boolean isRunning() {
+		return ! users.isEmpty();
+	}
+	
+	public Boolean isBreakout() {
+	  return isBreakout;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public boolean isForciblyEnded() {
+		return forciblyEnded;
+	}
+
+	public void setForciblyEnded(boolean forciblyEnded) {
+		this.forciblyEnded = forciblyEnded;
+	}
+
+	public String getExternalId() {
+		return extMeetingId;
+	}
+	
+	public String getInternalId() {
+		return intMeetingId;
+	}
+
+	public String getWebVoice() {
+		return webVoice;
+	}
+
+	public String getTelVoice() {
+		return telVoice;
+	}
+
+	public String getModeratorPassword() {
+		return moderatorPass;
+	}
+
+	public String getViewerPassword() {
+		return viewerPass;
+	}
+
+	public String getWelcomeMessage() {
+		return welcomeMsg;
+	}
+
+	public String getDefaultAvatarURL() {
+		return defaultAvatarURL;
+	}
+	
+	public String getLogoutUrl() {
+		return logoutUrl;
+	}
+
+	public int getMaxUsers() {
+		return maxUsers;
+	}
+
+	public boolean isRecord() {
+		return record;
+	}
+	
+	public boolean getAutoStartRecording() {
+		return autoStartRecording;
+	}
+	
+	public boolean getAllowStartStopRecording() {
+		return allowStartStopRecording;
+	}
+	
+	public boolean hasUserJoined() {
+		return userHasJoined;
+	}
+	
+	public void userJoined(User user) {
+	    userHasJoined = true;
+	    this.users.put(user.getInternalUserId(), user);
+	}
+
+	public User userLeft(String userid){
+		User u = (User) users.remove(userid);	
+		if (users.isEmpty()) lastUserLeftOn = System.currentTimeMillis();
+		return u;
+	}
+
+	public User getUserById(String id){
+		return this.users.get(id);
+	}
+	
+	public int getNumUsers(){
+		return this.users.size();
+	}
+	
+	public int getNumModerators(){
+		int sum = 0;
+		for (String key : users.keySet()) {
+		    User u =  (User) users.get(key);
+		    if (u.isModerator()) sum++;
+		}
+		return sum;
+	}
+	
+	public String getDialNumber() {
+		return dialNumber;
+	}
+	
+	public boolean wasNeverJoined(int expiry) {
+		return (hasStarted() && !hasEnded() && nobodyJoined(expiry));
+	}
+	
+	private boolean meetingInfinite() {
+		/* Meeting stays runs infinitely */
+	  return 	duration == 0;
+	}
+	
+	private boolean nobodyJoined(int expiry) {
+		if (expiry == 0) return false; /* Meeting stays created infinitely */
+		
+		long now = System.currentTimeMillis();
+
+		return (!userHasJoined && (now - createdTime) >  (expiry * MILLIS_IN_A_MINUTE));
+	}
+
+	private boolean hasBeenEmptyFor(int expiry) {
+		long now = System.currentTimeMillis();
+		return (now - lastUserLeftOn > (expiry * MILLIS_IN_A_MINUTE));
+	}
+	
+	private boolean isEmpty() {
+		return users.isEmpty();
+	}
+	
+	public boolean hasExpired(int expiry) {
+		return (hasStarted() && userHasJoined && isEmpty() && hasBeenEmptyFor(expiry));
+	}
+	
+	public boolean hasExceededDuration() {
+		return (hasStarted() && !hasEnded() && pastDuration());
+	}
+
+	private boolean pastDuration() {
+		if (meetingInfinite()) return false; 
+		long now = System.currentTimeMillis();
+		return (now - startTime > (duration * MILLIS_IN_A_MINUTE));
+	}
+	
+	private boolean hasStarted() {
+		return startTime > 0;
+	}
+	
+	private boolean hasEnded() {
+		return endTime > 0;
+	}
+
+	public int getNumListenOnly() {
+		int sum = 0;
+		for (String key : users.keySet()) {
+			User u =  (User) users.get(key);
+			if (u.isListeningOnly()) sum++;
+		}
+		return sum;
+	}
+	
+	public int getNumVoiceJoined() {
+		int sum = 0;
+		for (String key : users.keySet()) {
+			User u =  (User) users.get(key);
+			if (u.isVoiceJoined()) sum++;
+		}
+		return sum;
+	}
+
+	public int getNumVideos() {
+		int sum = 0;
+		for (String key : users.keySet()) {
+			User u =  (User) users.get(key);
+			sum += u.getStreams().size();
+		}
+		return sum;
+	}
+	
+	public void addUserCustomData(String userID, Map<String, String> data) {
+		userCustomData.put(userID, data);
+	}
+	
+	public Map<String, Object> getUserCustomData(String userID){
+		return (Map<String, Object>) userCustomData.get(userID);
+	}
+	
+	/***
+	 * Meeting Builder
+	 *
+	 */
+	public static class Builder {
+    	private String name;
+    	private String externalId;
+    	private String internalId;   	
+    	private int maxUsers;
+    	private boolean record;
+    	private boolean autoStartRecording;
+    	private boolean allowStartStopRecording;
+    	private String moderatorPass;
+    	private String viewerPass;
+    	private int duration;
+    	private String webVoice;
+    	private String telVoice;
+    	private String welcomeMsg;
+    	private String logoutUrl;
+    	private Map<String, String> metadata;
+    	private String dialNumber;
+    	private String defaultAvatarURL;
+    	private long createdTime;
+    	private boolean isBreakout;
+    	
+    	public Builder(String externalId, String internalId, long createTime) {
+    		this.externalId = externalId;
+    		this.internalId = internalId;
+    		this.createdTime = createTime;
+    	}
+    	
+    	public Builder withName(String name) {
+    		this.name = name;
+    		return this;
+    	}
+    	    	
+    	public Builder withDuration(int minutes) {
+    		duration = minutes;
+    		return this;
+    	}
+    	
+    	public Builder withMaxUsers(int n) {
+    		maxUsers = n;
+    		return this;
+    	}
+    	
+    	public Builder withRecording(boolean record) {
+    		this.record = record;
+    		return this;
+    	}
+    	
+    	public Builder withAutoStartRecording(boolean start) {
+    		this.autoStartRecording = start;
+    		return this;
+    	}
+
+    	public Builder withAllowStartStopRecording(boolean allow) {
+    		this.allowStartStopRecording = allow;
+    		return this;
+    	}
+    	
+    	public Builder withWebVoice(String w) {
+    		this.webVoice = w;
+    		return this;
+    	}
+    	
+    	public Builder withTelVoice(String t) {
+    		this.telVoice = t;
+    		return this;
+    	}
+    	
+    	public Builder withDialNumber(String d) {
+    		this.dialNumber = d;
+    		return this;
+    	}
+    	
+    	public Builder withModeratorPass(String p) {
+    		this.moderatorPass = p;
+    		return this;
+    	}
+    	
+    	public Builder withViewerPass(String p) {  		
+	    	this.viewerPass = p;
+	    	return this;
+	    }
+    	
+    	public Builder withWelcomeMessage(String w) {
+    		welcomeMsg = w;
+    		return this;
+    	}
+    	
+    	public Builder withDefaultAvatarURL(String w) {
+    		defaultAvatarURL = w;
+    		return this;
+    	}
+    	
+    	public Builder isBreakout(Boolean b) {
+    	  isBreakout = b;
+    	  return this;
+    	}
+    	   	
+    	public Builder withLogoutUrl(String l) {
+    		logoutUrl = l;
+    		return this;
+    	}
+    	
+    	public Builder withMetadata(Map<String, String> m) {
+    		metadata = m;
+    		return this;
+    	}
+    
+    	public Meeting build() {
+    		return new Meeting(this);
+    	}
+    }
+
+    public void userRegistered(String internalUserID) {
+        this.registeredUsers.put(internalUserID, new Long(System.nanoTime()));
+    }
+
+    public Long userUnregistered(String userid) {
+        String internalUserIDSeed = userid.split("_")[0];
+        Long r = (Long) this.registeredUsers.remove(internalUserIDSeed);
+        return r;
+    }
+
+    public ConcurrentMap<String, Long> getRegisteredUsers() {
+        return registeredUsers;
+    }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Playback.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Playback.java
new file mode 100755
index 0000000000..f847464fe6
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Playback.java
@@ -0,0 +1,60 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+import groovy.util.slurpersupport.GPathResult;
+
+public class Playback {
+	private String format;
+	private String url;
+	private int length;
+	private GPathResult extensions;
+	
+	public Playback(String format, String url, int length, GPathResult extensions) {
+		this.format = format;
+		this.url = url;
+		this.length = length;
+		this.extensions = extensions;
+	}
+	public String getFormat() {
+		return format;
+	}
+	public void setFormat(String format) {
+		this.format = format;
+	}
+	public String getUrl() {
+		return url;
+	}
+	public void setUrl(String url) {
+		this.url = url;
+	}
+	public int getLength() {
+		return length;
+	}
+	public void setLength(int length) {
+		this.length = length;
+	}
+	public GPathResult getExtensions() {
+		return extensions;
+	}
+	public void setExtensions(GPathResult extensions) {
+		this.extensions = extensions;
+	}
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Poll.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Poll.java
new file mode 100755
index 0000000000..6cd73574b8
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Poll.java
@@ -0,0 +1,81 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.util.HashMap;
+
+public class Poll{
+
+	private String meetingID;
+	private String pollID;
+	private String title;
+	private String question;
+	private String datetime;
+	private HashMap<String,String> answers;
+
+	public Poll(String meetingID, String title, String question){
+		this.pollID = generatePollID(meetingID);
+		this.meetingID = meetingID;
+		this.title = title;
+		this.question = question;
+		this.datetime = Long.toString(System.currentTimeMillis()); 
+		this.answers = new HashMap<String,String>();
+	}
+
+	public void addAnswer(String answer){
+		String answerID = generateAnswerID(this.meetingID);
+		this.answers.put(answerID,answer);
+	}
+
+	public void removeAnswer(String answerID){
+		this.answers.remove(answerID);
+	}
+
+	public String generatePollID(String meetingID){
+		return null;
+	}
+
+	public String generateAnswerID(String meetingID){
+		return null;
+	}
+
+	public void store() throws Exception{
+
+	}
+
+	public String getMeetingID(){
+		return this.meetingID;
+	}
+
+	public String getPollID(){
+		return this.pollID;
+	}
+
+	public HashMap<String,String> toMap(){
+		HashMap<String,String> map = new HashMap<String,String>();
+		map.put("pollID",pollID);
+		map.put("meetingID",meetingID);
+		map.put("title", title);
+		map.put("question",question);
+		map.put("datetime",datetime);
+		return map;
+	}
+
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recording.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recording.java
new file mode 100755
index 0000000000..5f2c82c456
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recording.java
@@ -0,0 +1,209 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import groovy.util.slurpersupport.GPathResult;
+
+public class Recording {
+	private String id;
+	private String meetingID;
+	private String name;
+	private boolean published;
+	private String startTime;
+	private String endTime;
+	private Map<String, String> metadata = new HashMap<String, String>();
+	private ArrayList<Playback> playbacks=new ArrayList<Playback>();
+	
+	//TODO: 
+	private String state;
+	private String playbackLink;
+	private String playbackFormat;
+	private String playbackDuration;
+	private GPathResult playbackExtensions;
+
+    public static final String STATE_PROCESSING = "processing";
+    public static final String STATE_PROCESSED = "processed";
+    public static final String STATE_PUBLISING = "publishing";
+    public static final String STATE_PUBLISHED = "published";
+    public static final String STATE_UNPUBLISING = "unpublishing";
+    public static final String STATE_UNPUBLISHED = "unpublished";
+    public static final String STATE_DELETING = "deleting";
+    public static final String STATE_DELETED = "deleted";
+
+	public String getId() {
+		return id;
+	}
+	
+	public void setId(String id) {
+		this.id = id;
+	}
+	
+	public String getState() {
+	    String state = this.state;
+	    if ( state == null || state.equals("") || state.equals("available") ) {
+	        state = isPublished()? STATE_PUBLISHED: STATE_UNPUBLISHED;
+	    }
+		return state;
+	}
+	
+	public void setState(String state) {
+		this.state = state;
+	}
+	
+	public boolean isPublished() {
+		return published;
+	}
+	
+	public void setPublished(boolean published) {
+		this.published = published;
+	}
+	
+	public String getStartTime() {
+		return startTime;
+	}
+	
+	public void setStartTime(String startTime) {
+		this.startTime = convertOldDateFormat(startTime);
+	}
+	
+	public String getEndTime() {
+		return endTime;
+	}
+	
+	public void setEndTime(String endTime) {
+		this.endTime = convertOldDateFormat(endTime);
+	}
+	
+	public String getPlaybackLink() {
+		return playbackLink;
+	}
+	
+	public void setPlaybackLink(String playbackLink) {
+		this.playbackLink = playbackLink;
+	}
+
+	public String getPlaybackFormat() {
+		return playbackFormat;
+	}
+
+	public void setPlaybackFormat(String playbackFormat) {
+		this.playbackFormat = playbackFormat;
+	}
+	
+	public String getPlaybackDuration() {
+		return playbackDuration;
+	}
+	
+	public void setPlaybackDuration(String playbackDuration) {
+		this.playbackDuration = playbackDuration;
+	}
+
+	public GPathResult getPlaybackExtensions() {
+		return playbackExtensions;
+	}
+
+	public void setPlaybackExtensions(GPathResult playbackExtensions) {
+		this.playbackExtensions = playbackExtensions;
+	}
+	
+	public Map<String, String> getMetadata() {
+		return metadata;
+	}
+	
+	public void setMetadata(Map<String, String> metadata) {
+		this.metadata = metadata;
+	}
+
+	public String getMeetingID() {
+		return meetingID;
+	}
+
+	public void setMeetingID(String meetingID) {
+		this.meetingID = meetingID;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public ArrayList<Playback> getPlaybacks() {
+		return playbacks;
+	}
+
+	public void setPlaybacks(ArrayList<Playback> playbacks) {
+		this.playbacks = playbacks;
+	}
+	
+	/* We used to have an old date format in the recordings 
+	 * e.g.: Thu Mar 04 14:05:56 UTC 2010
+	 * Now, we have a new one which it's a long string
+	 * This method converts the old date format to the new one */
+	
+	private String convertOldDateFormat(String olddate){
+		String newdate = olddate;
+
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy");
+			Calendar cal=Calendar.getInstance();
+			sdf.setLenient(false);
+			
+			cal.setTime(sdf.parse(olddate));
+			newdate = Long.toString(cal.getTimeInMillis());
+		} catch (ParseException pe) {
+			
+		}
+
+		return newdate;
+	}
+	
+}
+
+/*
+<recording>
+	<id>Demo Meeting-3243244</id>
+	<state>available</state>
+	<published>true</published>
+	<start_time>Thu Mar 04 14:05:56 UTC 2010</start_time>
+	<end_time>Thu Mar 04 15:01:01 UTC 2010</end_time>	
+	<playback>
+		<format>simple</format>
+		<link>http://server.com/simple/playback?recordingID=Demo Meeting-3243244</link> 	
+	</playback>
+	<meta>
+		<title>Test Recording 2</title>
+		<subject>English 232 session</subject>
+		<description>Second  test recording</description>
+		<creator>Omar Shammas</creator>
+		<contributor>Blindside</contributor>
+		<language>en_US</language>
+	</meta>
+</recording>
+*/
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recordings.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recordings.java
new file mode 100755
index 0000000000..69543097ed
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/Recordings.java
@@ -0,0 +1,44 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.io.File;
+import java.io.FileFilter;
+
+public class Recordings {
+	
+	public String[] getRecordings(String recordingDir) {
+		File dir = new File(recordingDir);
+
+		FileFilter fileFilter = new FileFilter() {
+		    public boolean accept(File file) {
+		        return file.isDirectory();
+		    }
+		};
+		
+		File[] dirs = dir.listFiles(fileFilter);
+		String[] meetings = new String[dirs.length];
+		
+		for (int i = 0; i < dirs.length; i++) {
+			meetings[i] = dirs[i].getName();
+		}
+		return meetings;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/User.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/User.java
new file mode 100755
index 0000000000..3f022263ac
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/User.java
@@ -0,0 +1,138 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class User {
+	private String internalUserId;
+	private String externalUserId;
+	private String fullname;
+	private String role;
+	private String avatarURL;
+	private Map<String,String> status;
+	private Boolean listeningOnly = false;
+	private Boolean voiceJoined = false;
+	private List<String> streams;
+	
+	public User(String internalUserId, String externalUserId, String fullname, String role, String avatarURL) {
+		this.internalUserId = internalUserId;
+		this.externalUserId = externalUserId;
+		this.fullname = fullname;
+		this.role = role;
+		this.avatarURL = avatarURL;
+		this.status = new ConcurrentHashMap<String, String>();
+		this.streams = Collections.synchronizedList(new ArrayList<String>());
+	}
+	
+	public String getInternalUserId() {
+		return this.internalUserId;
+	}
+	public void setInternalUserId(String internalUserId) {
+		this.internalUserId = internalUserId;
+	}
+	
+	public String getExternalUserId(){
+		return this.externalUserId;
+	}
+	
+	public void setExternalUserId(String externalUserId){
+		this.externalUserId = externalUserId;
+	}
+	
+	public String getFullname() {
+		return fullname;
+	}
+	public void setFullname(String fullname) {
+		this.fullname = fullname;
+	}
+	public String getRole() {
+		return role;
+	}
+	public void setRole(String role) {
+		this.role = role;
+	}
+
+	public String getAvatarUrl() {
+		return avatarURL;
+	}
+
+	public void setAvatarUrl(String avatarURL) {
+		this.avatarURL = avatarURL;
+	}
+
+	public boolean isModerator() {
+		return this.role.equalsIgnoreCase("MODERATOR");
+	}
+	
+	public void setStatus(String key, String value){
+		this.status.put(key, value);
+	}
+	public void removeStatus(String key){
+		this.status.remove(key);
+	}
+	public Map<String,String> getStatus(){
+		return this.status;
+	}
+
+	public boolean isPresenter() {
+		String isPresenter = this.status.get("presenter");
+		if (isPresenter != null) {
+			return isPresenter.equalsIgnoreCase("true");
+		}
+		return false;
+	}
+	
+	public void addStream(String stream) {
+		streams.add(stream);
+	}
+	
+	public void removeStream(String stream) {
+		streams.remove(stream);
+	}
+	
+	public List<String> getStreams() {
+		return streams;
+	}
+
+        public Boolean hasVideo() {
+                return this.getStreams().size() > 0;
+        }
+
+	public Boolean isListeningOnly() {
+		return listeningOnly;
+	}
+
+	public void setListeningOnly(Boolean listeningOnly) {
+		this.listeningOnly = listeningOnly;
+	}
+
+	public Boolean isVoiceJoined() {
+		return voiceJoined;
+	}
+
+	public void setVoiceJoined(Boolean voiceJoined) {
+		this.voiceJoined = voiceJoined;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/UserSession.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/UserSession.java
new file mode 100755
index 0000000000..346b9ecb95
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/domain/UserSession.java
@@ -0,0 +1,53 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.domain;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class UserSession {
+  public String authToken = null;
+  public String internalUserId = null;
+  public String conferencename = null;
+  public String meetingID = null;
+  public String externMeetingID = null;
+  public String externUserID = null;
+  public String fullname = null; 
+  public String role = null;
+  public String conference = null;
+  public String room = null;
+  public String voicebridge = null;
+  public String webvoiceconf = null;
+  public String mode = null;
+  public String record = null;
+  public String welcome = null;
+  public String logoutUrl = null;
+  public String defaultLayout = "NOLAYOUT";
+  public String avatarURL;
+  public String configXML;
+  
+  private AtomicInteger connections = new AtomicInteger(0);
+  
+ 
+  public synchronized int incrementConnectionNum() {
+    return connections.incrementAndGet();
+  }
+  
+  
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/Constants.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/Constants.java
new file mode 100755
index 0000000000..fed0e687c0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/Constants.java
@@ -0,0 +1,96 @@
+package org.bigbluebutton.api.messaging;
+
+public class Constants {
+  public static final String NAME                            = "name";
+  public static final String HEADER                          = "header";
+  public static final String PAYLOAD                         = "payload";
+  public static final String MEETING_ID                      = "meeting_id";
+  public static final String EXTERNAL_MEETING_ID             = "external_meeting_id";
+  public static final String TIMESTAMP                       = "timestamp";
+  public static final String USER_ID                         = "userid";
+  public static final String RECORDED                        = "recorded";
+  public static final String MEETING_NAME                    = "meeting_name";
+  public static final String VOICE_CONF                      = "voice_conf";
+  public static final String DURATION                        = "duration";
+  public static final String AUTH_TOKEN                      = "auth_token";
+  public static final String ROLE                            = "role";
+  public static final String EXT_USER_ID                     = "external_user_id";
+  public static final String REQUESTER_ID                    = "requester_id";
+  public static final String REPLY_TO                        = "reply_to";
+  public static final String LOWERED_BY                      = "lowered_by";
+  public static final String STREAM                          = "stream";
+  public static final String LOCKED                          = "locked";
+  public static final String SETTINGS                        = "settings";
+  public static final String LOCK                            = "lock";
+  public static final String EXCEPT_USERS                    = "except_users";
+  public static final String STATUS                          = "status";
+  public static final String VALUE                           = "value";
+  public static final String NEW_PRESENTER_ID                = "new_presenter_id";
+  public static final String NEW_PRESENTER_NAME              = "new_presenter_name";
+  public static final String ASSIGNED_BY                     = "assigned_by";
+  public static final String RECORDING                       = "recording";
+  public static final String AUTO_START_RECORDING            = "auto_start_recording";
+  public static final String ALLOW_START_STOP_RECORDING      = "allow_start_stop_recording";
+  public static final String LAYOUT_ID                       = "layout_id";
+  public static final String POLL                            = "poll";
+  public static final String POLL_ID                         = "poll_id";
+  public static final String FORCE                           = "force";
+  public static final String RESPONSE                        = "response";
+  public static final String PRESENTATION_ID                 = "presentation_id";
+  public static final String X_OFFSET                        = "x_offset";
+  public static final String Y_OFFSET                        = "y_offset";
+  public static final String WIDTH_RATIO                     = "width_ratio";
+  public static final String HEIGHT_RATIO                    = "height_ratio";
+  public static final String PAGE                            = "page";
+  public static final String SHARE                           = "share";
+  public static final String PRESENTATIONS                   = "presentations";
+  public static final String MESSAGE_KEY                     = "message_key";
+  public static final String CODE                            = "code";
+  public static final String PRESENTATION_NAME               = "presentation_name";
+  public static final String NUM_PAGES                       = "num_pages";
+  public static final String MAX_NUM_PAGES                   = "max_num_pages";
+  public static final String PAGES_COMPLETED                 = "pages_completed";
+  public static final String MUTE                            = "mute";
+  public static final String CALLER_ID_NUM                   = "caller_id_num";
+  public static final String CALLER_ID_NAME                  = "caller_id_name";
+  public static final String TALKING                         = "talking";
+  public static final String USER                            = "user";
+  public static final String MUTED                           = "muted";
+  public static final String VOICE_USER                      = "voice_user";
+  public static final String RECORDING_FILE                  = "recording_file";
+  public static final String ANNOTATION                      = "annotation";
+  public static final String WHITEBOARD_ID                   = "whiteboard_id";
+  public static final String ENABLE                          = "enable";
+  public static final String PRESENTER                       = "presenter";
+  public static final String USERS                           = "users";
+  public static final String RAISE_HAND                      = "raise_hand";
+  public static final String HAS_STREAM                      = "has_stream";
+  public static final String WEBCAM_STREAM                   = "webcam_stream";
+  public static final String PHONE_USER                      = "phone_user"; 
+  public static final String PERMISSIONS                     = "permissions";
+  public static final String VALID                           = "valid";
+  public static final String CHAT_HISTORY                    = "chat_history";
+  public static final String MESSAGE                         = "message";
+  public static final String SET_BY_USER_ID                  = "set_by_user_id";
+  public static final String POLLS                           = "polls";
+  public static final String REASON                          = "reason";
+  public static final String RESPONDER                       = "responder";
+  public static final String PRESENTATION_INFO               = "presentation_info";
+  public static final String SHAPES                          = "shapes"; 
+  public static final String SHAPE                           = "shape";
+  public static final String SHAPE_ID                        = "shape_id";    
+  public static final String PRESENTATION                    = "presentation";
+  public static final String ID                              = "id";
+  public static final String CURRENT                         = "current";
+  public static final String PAGES                           = "pages";
+  public static final String WEB_USER_ID                     = "web_user_id";
+  public static final String JOINED                          = "joined";
+  public static final String X_PERCENT                       = "x_percent";
+  public static final String Y_PERCENT                       = "y_percent";
+  public static final String KEEP_ALIVE_ID                   = "keep_alive_id"; 
+  public static final String MODERATOR_PASS                  = "moderator_pass";
+  public static final String VIEWER_PASS                     = "viewer_pass";
+  public static final String CREATE_TIME                     = "create_time";
+  public static final String CREATE_DATE                     = "create_date";
+  public static final String AVATAR_URL                      = "avatarURL";
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java
new file mode 100755
index 0000000000..c71253b8db
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MeetingMessageHandler.java
@@ -0,0 +1,194 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.Set;
+
+import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom;
+import org.bigbluebutton.api.messaging.messages.EndBreakoutRoom;
+import org.bigbluebutton.api.messaging.messages.IMessage;
+import org.bigbluebutton.api.messaging.messages.KeepAliveReply;
+import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
+import org.bigbluebutton.api.messaging.messages.MeetingEnded;
+import org.bigbluebutton.api.messaging.messages.MeetingStarted;
+import org.bigbluebutton.api.messaging.messages.UserJoined;
+import org.bigbluebutton.api.messaging.messages.UserJoinedVoice;
+import org.bigbluebutton.api.messaging.messages.UserLeft;
+import org.bigbluebutton.api.messaging.messages.UserLeftVoice;
+import org.bigbluebutton.api.messaging.messages.UserListeningOnly;
+import org.bigbluebutton.api.messaging.messages.UserSharedWebcam;
+import org.bigbluebutton.api.messaging.messages.UserStatusChanged;
+import org.bigbluebutton.api.messaging.messages.UserUnsharedWebcam;
+import org.bigbluebutton.api.messaging.messages.*;
+import org.bigbluebutton.common.converters.FromJsonDecoder;
+import org.bigbluebutton.common.messages.IBigBlueButtonMessage;
+import org.bigbluebutton.common.messages.PubSubPongMessage;
+import org.bigbluebutton.messages.CreateBreakoutRoomRequest;
+import org.bigbluebutton.messages.EndBreakoutRoomRequest;
+import org.bigbluebutton.common.messages.SendStunTurnInfoRequestMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class MeetingMessageHandler implements MessageHandler {
+  private static Logger log = LoggerFactory.getLogger(MeetingMessageHandler.class);
+
+  private Set<MessageListener> listeners;
+  private final FromJsonDecoder decoder = new FromJsonDecoder();
+  
+  public void setMessageListeners(Set<MessageListener> listeners) {
+    this.listeners = listeners;
+  }
+  
+  public void handleMessage(String pattern, String channel, String message) {	
+    JsonParser parser = new JsonParser();
+    JsonObject obj = (JsonObject) parser.parse(message);
+
+    if (channel.equalsIgnoreCase(MessagingConstants.FROM_MEETING_CHANNEL)) {
+      if (obj.has("header") && obj.has("payload")) {
+        JsonObject header = (JsonObject) obj.get("header");
+        JsonObject payload = (JsonObject) obj.get("payload");
+
+        if (header.has("name")) {
+          String messageName = header.get("name").getAsString();
+          if(MessagingConstants.MEETING_STARTED_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new MeetingStarted(meetingId));
+            }
+          } else if(MessagingConstants.MEETING_ENDED_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new MeetingEnded(meetingId));
+            }
+          } else if (MessagingConstants.MEETING_DESTROYED_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            log.info("Received a meeting destroyed message for meeting id=[{}]", meetingId);
+            for (MessageListener listener : listeners) {
+              listener.handle(new MeetingDestroyed(meetingId));
+            }
+          } else if (CreateBreakoutRoomRequest.NAME.equals(messageName)) {
+            CreateBreakoutRoomRequest msg = new Gson().fromJson(message, CreateBreakoutRoomRequest.class);
+            for (MessageListener listener : listeners) {
+              listener.handle(new CreateBreakoutRoom(
+                  msg.payload.breakoutId, 
+                  msg.payload.parentId,
+                  msg.payload.name, 
+                  msg.payload.voiceConfId, 
+                  msg.payload.viewerPassword, 
+                  msg.payload.moderatorPassword, 
+                  msg.payload.durationInMinutes, 
+                  msg.payload.defaultPresentationURL));
+              
+            }
+          }
+          else if (EndBreakoutRoomRequest.NAME.equals(messageName)) {
+            EndBreakoutRoomRequest msg = new Gson().fromJson(message, EndBreakoutRoomRequest.class);
+            log.info("Received an end breakout room request message for breakout meeting id=[{}]", msg.payload.meetingId);
+            for (MessageListener listener : listeners) {
+              listener.handle(new EndBreakoutRoom(msg.payload.meetingId));
+            }
+          }
+        }
+      }
+    } else if (channel.equalsIgnoreCase(MessagingConstants.FROM_SYSTEM_CHANNEL)) {
+      if (obj.has("header") && obj.has("payload")) {
+        JsonObject header = (JsonObject) obj.get("header");
+        JsonObject payload = (JsonObject) obj.get("payload");
+        if (header.has("name")) {
+          String messageName = header.get("name").getAsString();
+          IMessage rxMsg = null;
+          if (PubSubPongMessage.PUBSUB_PONG.equals(messageName)) {
+            IBigBlueButtonMessage msg = decoder.decodeMessage(message);
+            if (msg != null) {
+              PubSubPongMessage m = (PubSubPongMessage) msg;
+              rxMsg = new KeepAliveReply(m.payload.system, m.payload.timestamp);
+            }
+          }
+          if (rxMsg != null) {
+            for (MessageListener listener : listeners) {
+              listener.handle(rxMsg);
+            } 
+          }
+        }
+      }
+    } else if (channel.equalsIgnoreCase(MessagingConstants.FROM_USERS_CHANNEL)) {	
+      if (obj.has("header") && obj.has("payload")) {
+        JsonObject header = (JsonObject) obj.get("header");
+        JsonObject payload = (JsonObject) obj.get("payload");
+        if (header.has("name")) {
+          String messageName = header.get("name").getAsString();
+          if (MessagingConstants.USER_JOINED_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            JsonObject user = (JsonObject) payload.get("user");
+            String userid = user.get("userid").getAsString();
+            String externuserid = user.get("extern_userid").getAsString();
+            String username = user.get("name").getAsString();
+            String role = user.get("role").getAsString();
+            String avatarURL = user.get("avatarURL").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserJoined(meetingId, userid, externuserid, username, role, avatarURL));
+            }
+          } else if(MessagingConstants.USER_STATUS_CHANGE_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            String userid = payload.get("userid").getAsString();
+            String status = payload.get("status").getAsString();
+            String value = payload.get("value").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserStatusChanged(meetingId, userid, status, value));
+            }
+          } else if (MessagingConstants.USER_LEFT_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            JsonObject user = (JsonObject) payload.get("user");
+            String userid = user.get("userid").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserLeft(meetingId, userid));
+            }
+          } else if (MessagingConstants.USER_JOINED_VOICE_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            JsonObject user = (JsonObject) payload.get("user");
+            String userid = user.get("userid").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserJoinedVoice(meetingId, userid));
+            }
+          } else if (MessagingConstants.USER_LEFT_VOICE_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            JsonObject user = (JsonObject) payload.get("user");
+            String userid = user.get("userid").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserLeftVoice(meetingId, userid));
+            }
+          } else if (MessagingConstants.USER_LISTEN_ONLY_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            String userid = payload.get("userid").getAsString();
+            Boolean listenOnly = payload.get("listen_only").getAsBoolean();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserListeningOnly(meetingId, userid, listenOnly));
+            }
+          } else if (MessagingConstants.USER_SHARE_WEBCAM_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            String userid = payload.get("userid").getAsString();
+            String stream = payload.get("stream").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserSharedWebcam(meetingId, userid, stream));
+            }
+          } else if (MessagingConstants.USER_UNSHARE_WEBCAM_EVENT.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get("meeting_id").getAsString();
+            String userid = payload.get("userid").getAsString();
+            String stream = payload.get("stream").getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new UserUnsharedWebcam(meetingId, userid, stream));
+            }
+          } else if (SendStunTurnInfoRequestMessage.SEND_STUN_TURN_INFO_REQUEST_MESSAGE.equalsIgnoreCase(messageName)) {
+            String meetingId = payload.get(Constants.MEETING_ID).getAsString();
+            String requesterId = payload.get(Constants.REQUESTER_ID).getAsString();
+            for (MessageListener listener : listeners) {
+              listener.handle(new StunTurnInfoRequested(meetingId, requesterId));
+            }
+          }
+        }
+      }
+    } 
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageBuilder.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageBuilder.java
new file mode 100755
index 0000000000..dfdf3e43a0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageBuilder.java
@@ -0,0 +1,36 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.concurrent.TimeUnit;
+
+import com.google.gson.Gson;
+
+public class MessageBuilder {
+  public final static String VERSION = "version";
+
+  public static long generateTimestamp() {
+    return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+  }
+  
+  public static java.util.HashMap<String, Object> buildHeader(String name, String version, String replyTo) {
+  	java.util.HashMap<String, Object> header = new java.util.HashMap<String, Object>();
+    header.put(Constants.NAME, name);
+    header.put(VERSION, version);
+    header.put(Constants.TIMESTAMP, generateTimestamp());
+    if (replyTo != null && replyTo != "")
+      header.put(Constants.REPLY_TO, replyTo);
+    
+    return header;
+  }
+  
+  
+  public static String buildJson(java.util.HashMap<String, Object> header, 
+  		java.util.HashMap<String, Object> payload) {
+    
+  	java.util.HashMap<String, java.util.HashMap<String, Object>> message = new java.util.HashMap<String, java.util.HashMap<String, Object>>();
+    message.put(Constants.HEADER, header);
+    message.put(Constants.PAYLOAD, payload);
+    
+    Gson gson = new Gson();
+    return gson.toJson(message);
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java
new file mode 100755
index 0000000000..d8cd308c9d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java
@@ -0,0 +1,25 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.Set;
+
+public class MessageDistributor {
+	private ReceivedMessageHandler handler;
+	private Set<MessageHandler> listeners;
+	
+	public void setMessageListeners(Set<MessageHandler> listeners) {
+		this.listeners = listeners;
+	}
+	
+	public void setMessageHandler(ReceivedMessageHandler handler) {
+		this.handler = handler;
+		if (handler != null) {
+			handler.setMessageDistributor(this);
+		}		
+	}
+	
+	public void notifyListeners(String pattern, String channel, String message) {
+		for (MessageHandler listener : listeners) {
+			listener.handleMessage(pattern, channel, message);
+		}		
+	}	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageHandler.java
new file mode 100755
index 0000000000..a6fe30d289
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageHandler.java
@@ -0,0 +1,23 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.api.messaging;
+
+public interface MessageHandler {
+	void handleMessage(String pattern, String channel, String message);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageListener.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageListener.java
new file mode 100755
index 0000000000..4779ea975b
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageListener.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.messaging;
+
+import org.bigbluebutton.api.messaging.messages.IMessage;
+
+public interface MessageListener {
+	void handle(IMessage message);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageReceiver.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageReceiver.java
new file mode 100755
index 0000000000..9be59010d4
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageReceiver.java
@@ -0,0 +1,114 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPubSub;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+
+public class MessageReceiver {
+	private static Logger log = LoggerFactory.getLogger(MessageReceiver.class);
+	
+	private ReceivedMessageHandler handler;
+	
+	private Jedis jedis;
+	private volatile boolean receiveMessage = false;
+	
+	private final Executor msgReceiverExec = Executors.newSingleThreadExecutor();
+	private final Executor runExec = Executors.newSingleThreadExecutor();
+	
+	private String host;
+	private int port;
+	
+	public void stop() {
+		receiveMessage = false;
+	}
+	
+	public void start() {
+		log.info("Ready to receive messages from Redis pubsub.");
+		try {
+			receiveMessage = true;
+			jedis = new Jedis(host, port);
+			// Set the name of this client to be able to distinguish when doing
+			// CLIENT LIST on redis-cli
+			jedis.clientSetname("BbbWebSub");
+			
+			Runnable messageReceiver = new Runnable() {
+			    public void run() {
+			    	if (receiveMessage) {
+			    		try {
+			    			jedis.psubscribe(new PubSubListener(), MessagingConstants.FROM_BBB_APPS_PATTERN);
+			    		} catch(JedisConnectionException ex) {
+			    			log.warn("Exception on Jedis connection. Resubscribing to pubsub.");
+			    			start();
+			    		}			    		 
+			    	}
+			    }
+			};
+			msgReceiverExec.execute(messageReceiver);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	public void setHost(String host){
+		this.host = host;
+	}
+	
+	public void setPort(int port) {
+		this.port = port;
+	}
+	
+	public void setMessageHandler(ReceivedMessageHandler handler) {
+		this.handler = handler;
+	}
+	
+	private class PubSubListener extends JedisPubSub {
+		
+		public PubSubListener() {
+			super();			
+		}
+
+		@Override
+		public void onMessage(String channel, String message) {
+			// Not used.
+		}
+
+		@Override
+		public void onPMessage(final String pattern, final String channel, final String message) {
+			Runnable task = new Runnable() {
+		    public void run() {
+		    	handler.handleMessage(pattern, channel, message);	
+		    }
+			};
+			
+			runExec.execute(task);		
+		}
+
+		@Override
+		public void onPSubscribe(String pattern, int subscribedChannels) {
+			log.debug("Subscribed to the pattern: " + pattern);
+		}
+
+		@Override
+		public void onPUnsubscribe(String pattern, int subscribedChannels) {
+			// Not used.
+		}
+
+		@Override
+		public void onSubscribe(String channel, int subscribedChannels) {
+			// Not used.
+		}
+
+		@Override
+		public void onUnsubscribe(String channel, int subscribedChannels) {
+			// Not used.
+		}		
+	}
+}
+
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageSender.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageSender.java
new file mode 100755
index 0000000000..5f3b830f6f
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageSender.java
@@ -0,0 +1,106 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.Protocol;
+
+public class MessageSender {
+	private static Logger log = LoggerFactory.getLogger(MessageSender.class);
+	
+	private JedisPool redisPool;
+	private volatile boolean sendMessage = false;
+	
+	private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
+	private final Executor runExec = Executors.newSingleThreadExecutor();
+	private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>();
+	
+	private String host;
+	private int port;
+	
+	public void stop() {
+		sendMessage = false;
+		redisPool.destroy();
+	}
+		
+	public void start() {	
+		GenericObjectPoolConfig config = new GenericObjectPoolConfig();
+		config.setMaxTotal(32);
+		config.setMaxIdle(8);
+		config.setMinIdle(1);
+		config.setTestOnBorrow(true);
+		config.setTestOnReturn(true);
+		config.setTestWhileIdle(true);
+		config.setNumTestsPerEvictionRun(12);
+		config.setMaxWaitMillis(5000);
+		config.setTimeBetweenEvictionRunsMillis(60000);
+		config.setBlockWhenExhausted(true);
+		
+		// Set the name of this client to be able to distinguish when doing
+		// CLIENT LIST on redis-cli
+		redisPool = new JedisPool(config, host, port, Protocol.DEFAULT_TIMEOUT, null,
+		        Protocol.DEFAULT_DATABASE, "BbbWebPub");
+		
+		log.info("Redis message publisher starting!");
+		try {
+			sendMessage = true;
+			
+			Runnable messageSender = new Runnable() {
+			  public void run() {
+			   	while (sendMessage) {
+				   	try {
+				      MessageToSend msg = messages.take();
+							publish(msg.getChannel(), msg.getMessage());
+						} catch (InterruptedException e) {
+							log.warn("Failed to get message from queue.");
+						}    			    		
+			    }
+			  }
+			};
+			msgSenderExec.execute(messageSender);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	public void send(String channel, String message) {
+		MessageToSend msg = new MessageToSend(channel, message);
+		messages.add(msg);
+	}
+	
+	private void publish(final String channel, final String message) {
+		Runnable task = new Runnable() {
+		  public void run() {
+				Jedis jedis = redisPool.getResource();
+				try {
+					if(channel.equalsIgnoreCase("bigbluebutton:from-bbb-apps:users") || channel.equalsIgnoreCase("bigbluebutton:from-bbb-apps:meeting"))
+						log.info("web-Publishing..." + channel + ":" + message);
+					jedis.publish(channel, message);
+				} catch(Exception e){
+					log.warn("Cannot publish the message to pubsub", e);
+				} finally {
+					if (jedis != null) {
+						jedis.close();
+					}
+					
+				}		  	
+		  }
+		};
+		
+		runExec.execute(task);
+	}
+	
+	public void setHost(String host){
+		this.host = host;
+	}
+	
+	public void setPort(int port) {
+		this.port = port;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToJson.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToJson.java
new file mode 100755
index 0000000000..98649191b5
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToJson.java
@@ -0,0 +1,72 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.HashMap;
+
+import org.bigbluebutton.api.messaging.converters.messages.CreateMeetingMessage;
+import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage;
+import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage;
+import org.bigbluebutton.api.messaging.converters.messages.KeepAliveMessage;
+import org.bigbluebutton.api.messaging.converters.messages.RegisterUserMessage;
+
+public class MessageToJson {
+
+	public static String registerUserToJson(RegisterUserMessage message) {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.MEETING_ID, message.meetingID);
+		payload.put(Constants.NAME, message.fullname);
+		payload.put(Constants.USER_ID, message.internalUserId);
+		payload.put(Constants.ROLE, message.role);
+		payload.put(Constants.EXT_USER_ID, message.externUserID);
+		payload.put(Constants.AUTH_TOKEN, message.authToken);
+		payload.put(Constants.AVATAR_URL, message.avatarURL);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(RegisterUserMessage.REGISTER_USER, message.VERSION, null);
+
+		return MessageBuilder.buildJson(header, payload);		
+	}
+	
+	public static String createMeetingMessageToJson(CreateMeetingMessage msg) {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.MEETING_ID, msg.id);
+		payload.put(Constants.EXTERNAL_MEETING_ID, msg.externalId);
+		payload.put(Constants.NAME, msg.name);
+		payload.put(Constants.RECORDED, msg.record);
+		payload.put(Constants.VOICE_CONF, msg.voiceBridge);
+		payload.put(Constants.DURATION, msg.duration);
+		payload.put(Constants.AUTO_START_RECORDING, msg.autoStartRecording);
+		payload.put(Constants.ALLOW_START_STOP_RECORDING, msg.allowStartStopRecording);
+		payload.put(Constants.MODERATOR_PASS, msg.moderatorPass);
+		payload.put(Constants.VIEWER_PASS, msg.viewerPass);
+		payload.put(Constants.CREATE_TIME, msg.createTime);
+		payload.put(Constants.CREATE_DATE, msg.createDate);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(CreateMeetingMessage.CREATE_MEETING_REQUEST_EVENT, CreateMeetingMessage.VERSION, null);
+		return MessageBuilder.buildJson(header, payload);				
+	}
+	
+	public static String destroyMeetingMessageToJson(DestroyMeetingMessage msg) {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.MEETING_ID, msg.meetingId);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DestroyMeetingMessage.DESTROY_MEETING_REQUEST_EVENT, DestroyMeetingMessage.VERSION, null);
+		return MessageBuilder.buildJson(header, payload);				
+	}	
+	
+	public static String endMeetingMessageToJson(EndMeetingMessage msg) {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.MEETING_ID, msg.meetingId);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(EndMeetingMessage.END_MEETING_REQUEST_EVENT, EndMeetingMessage.VERSION, null);
+		return MessageBuilder.buildJson(header, payload);				
+	}	
+
+	public static String keepAliveMessageToJson(KeepAliveMessage msg) {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.KEEP_ALIVE_ID, msg.keepAliveId);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(KeepAliveMessage.KEEP_ALIVE_REQUEST, KeepAliveMessage.VERSION, null);
+		return MessageBuilder.buildJson(header, payload);				
+	}	
+	
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToSend.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToSend.java
new file mode 100755
index 0000000000..58fc7690a1
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessageToSend.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.api.messaging;
+
+public class MessageToSend {
+	private final String channel;
+	private final String message;
+	
+	public MessageToSend(String channel, String message) {
+		this.channel = channel;
+		this.message = message;
+	}
+	
+	public String getChannel() {
+		return channel;
+	}
+	
+	public String getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingConstants.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingConstants.java
new file mode 100755
index 0000000000..a57cf06c6d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingConstants.java
@@ -0,0 +1,60 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.messaging;
+
+public class MessagingConstants {
+	
+	public static final String FROM_BBB_APPS_CHANNEL       = "bigbluebutton:from-bbb-apps";
+	public static final String FROM_BBB_APPS_PATTERN       = FROM_BBB_APPS_CHANNEL + ":*";
+	public static final String FROM_SYSTEM_CHANNEL         = FROM_BBB_APPS_CHANNEL + ":system";
+	public static final String FROM_MEETING_CHANNEL        = FROM_BBB_APPS_CHANNEL + ":meeting";
+  public static final String FROM_PRESENTATION_CHANNEL   = FROM_BBB_APPS_CHANNEL + ":presentation";
+  public static final String FROM_POLLING_CHANNEL        = FROM_BBB_APPS_CHANNEL + ":polling";
+  public static final String FROM_USERS_CHANNEL          = FROM_BBB_APPS_CHANNEL + ":users";
+  public static final String FROM_CHAT_CHANNEL           = FROM_BBB_APPS_CHANNEL + ":chat"; 
+  
+  
+	public static final String TO_BBB_APPS_CHANNEL         = "bigbluebutton:to-bbb-apps";	
+	public static final String TO_BBB_APPS_PATTERN         = TO_BBB_APPS_CHANNEL + ":*";
+	public static final String TO_MEETING_CHANNEL          = TO_BBB_APPS_CHANNEL + ":meeting";	
+	public static final String TO_SYSTEM_CHANNEL           = TO_BBB_APPS_CHANNEL + ":system";
+  public static final String TO_PRESENTATION_CHANNEL     = TO_BBB_APPS_CHANNEL + ":presentation";
+  public static final String TO_POLLING_CHANNEL          = TO_BBB_APPS_CHANNEL + ":polling";
+  public static final String TO_USERS_CHANNEL            = TO_BBB_APPS_CHANNEL + ":users";
+  public static final String TO_CHAT_CHANNEL             = TO_BBB_APPS_CHANNEL + ":chat";   
+
+	
+	public static final String MEETING_STARTED_EVENT         = "meeting_created_message";
+	public static final String MEETING_ENDED_EVENT           = "meeting_ended_message";
+	public static final String MEETING_DESTROYED_EVENT       = "meeting_destroyed_event";
+	public static final String USER_JOINED_EVENT             = "user_joined_message";
+	public static final String USER_LEFT_EVENT               = "user_left_message";
+	public static final String USER_STATUS_CHANGE_EVENT      = "user_status_changed_message";
+	public static final String USER_JOINED_VOICE_EVENT       = "user_joined_voice_message";
+	public static final String USER_LEFT_VOICE_EVENT         = "user_left_voice_message";
+	public static final String USER_LISTEN_ONLY_EVENT        = "user_listening_only";
+	public static final String USER_SHARE_WEBCAM_EVENT       = "user_shared_webcam_message";
+	public static final String USER_UNSHARE_WEBCAM_EVENT     = "user_unshared_webcam_message";
+			
+	public static final String SEND_POLLS_EVENT = "SendPollsEvent";
+	public static final String KEEP_ALIVE_REPLY = "keep_alive_reply";
+	
+	public static final String BBB_APPS_KEEP_ALIVE_CHANNEL = "bigbluebutton:from-bbb-apps:keepalive";
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingService.java
new file mode 100755
index 0000000000..19f8ef24db
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/MessagingService.java
@@ -0,0 +1,45 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.messaging;
+
+import org.bigbluebutton.web.services.turn.StunServer;
+import org.bigbluebutton.web.services.turn.TurnEntry;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface MessagingService {	
+	void recordMeetingInfo(String meetingId, Map<String, String> info);
+	void destroyMeeting(String meetingID);
+	void createMeeting(String meetingID, String externalMeetingID, String meetingName, Boolean recorded, 
+			      String voiceBridge, Integer duration, Boolean autoStartRecording,
+			      Boolean allowStartStopRecording, String moderatorPass, String viewerPass,
+			      Long createTime, String createDate, Boolean isBreakout);
+	void endMeeting(String meetingId);
+	void send(String channel, String message);
+	void sendPolls(String meetingId, String title, String question, String questionType, List<String> answers);
+	String storeSubscription(String meetingId, String externalMeetingID, String callbackURL);
+	boolean removeSubscription(String meetingId, String subscriptionId);
+	List<Map<String,String>> listSubscriptions(String meetingId);
+	void registerUser(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, String avatarURL);
+	void sendKeepAlive(String system, Long timestamp);
+	void sendStunTurnInfo(String meetingId, String internalUserId, Set<StunServer> stuns, Set<TurnEntry> turns);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessage.java
new file mode 100755
index 0000000000..88e8b2cead
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessage.java
@@ -0,0 +1,25 @@
+package org.bigbluebutton.api.messaging;
+
+public class ReceivedMessage {
+	private final String pattern;
+	private final String channel;
+	private final String message;
+	
+	public ReceivedMessage(String pattern, String channel, String message) {
+		this.pattern = pattern;
+		this.channel = channel;
+		this.message = message;
+	}
+
+	public String getPattern() {
+		return pattern;
+	}
+
+	public String getChannel() {
+		return channel;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java
new file mode 100755
index 0000000000..98e35df750
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java
@@ -0,0 +1,74 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ReceivedMessageHandler {
+	private static Logger log = LoggerFactory.getLogger(ReceivedMessageHandler.class);
+	
+	private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>();
+	
+	private volatile boolean processMessage = false;
+	
+	private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
+	private final Executor runExec = Executors.newSingleThreadExecutor();
+	
+	private MessageDistributor handler;
+	
+	public void stop() {
+		processMessage = false;
+	}
+	
+	public void start() {	
+		log.info("Ready to handle messages from Redis pubsub!");
+
+		try {
+			processMessage = true;
+			
+			Runnable messageProcessor = new Runnable() {
+			    public void run() {
+			    	while (processMessage) {
+			    		try {
+							ReceivedMessage msg = receivedMessages.take();
+							processMessage(msg);
+						} catch (InterruptedException e) {
+							log.warn("Error while taking received message from queue.");
+						}   			    		
+			    	}
+			    }
+			};
+			msgProcessorExec.execute(messageProcessor);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	private void processMessage(final ReceivedMessage msg) {
+		Runnable task = new Runnable() {
+	    public void run() {
+	  		if (handler != null) {
+//	  			log.debug("Let's process this message: " + msg.getMessage());
+
+	  			handler.notifyListeners(msg.getPattern(), msg.getChannel(), msg.getMessage());
+	  		} else {
+	  			log.warn("No listeners interested in messages from Redis!");
+	  		}	    	
+	    }
+		};
+		
+		runExec.execute(task);
+	}
+	
+	public void handleMessage(String pattern, String channel, String message) {
+		ReceivedMessage rm = new ReceivedMessage(pattern, channel, message);
+		receivedMessages.add(rm);
+	}
+	
+	public void setMessageDistributor(MessageDistributor h) {
+		this.handler = h;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisMessagingService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisMessagingService.java
new file mode 100755
index 0000000000..206df68b15
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisMessagingService.java
@@ -0,0 +1,173 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.api.messaging;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.*;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.imageio.ImageIO;
+import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage;
+import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage;
+import org.bigbluebutton.api.messaging.converters.messages.RegisterUserMessage;
+import org.bigbluebutton.common.converters.ToJsonEncoder;
+import org.bigbluebutton.common.messages.MessagingConstants;
+import org.bigbluebutton.messages.CreateMeetingRequest;
+import org.bigbluebutton.messages.CreateMeetingRequest.CreateMeetingRequestPayload;
+import org.bigbluebutton.common.messages.Constants;
+import org.bigbluebutton.common.messages.PubSubPingMessage;
+import org.bigbluebutton.common.messages.payload.PubSubPingMessagePayload;
+import org.bigbluebutton.common.messages.SendStunTurnInfoReplyMessage;
+import org.bigbluebutton.web.services.turn.StunServer;
+import org.bigbluebutton.web.services.turn.TurnEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.google.gson.Gson;
+
+public class RedisMessagingService implements MessagingService {
+	private static Logger log = LoggerFactory.getLogger(RedisMessagingService.class);
+	
+	private RedisStorageService storeService;
+	private MessageSender sender;
+	private ToJsonEncoder encoder = new ToJsonEncoder();
+	
+	public void recordMeetingInfo(String meetingId, Map<String, String> info) {
+		storeService.recordMeetingInfo(meetingId, info);
+	}
+
+	public void destroyMeeting(String meetingID) {
+		DestroyMeetingMessage msg = new DestroyMeetingMessage(meetingID);
+		String json = MessageToJson.destroyMeetingMessageToJson(msg);
+		log.info("Sending destroy meeting message to bbb-apps:[{}]", json);
+		sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);	
+	}
+	
+	public void registerUser(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, String avatarURL) {
+		RegisterUserMessage msg = new RegisterUserMessage(meetingID, internalUserId, fullname, role, externUserID, authToken, avatarURL);
+		String json = MessageToJson.registerUserToJson(msg);
+		log.info("Sending register user message to bbb-apps:[{}]", json);
+		sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);		
+	}
+	
+	public void createMeeting(String meetingID, String externalMeetingID, String meetingName, Boolean recorded, 
+			                      String voiceBridge, Integer duration, 
+			                      Boolean autoStartRecording, Boolean allowStartStopRecording,
+			                      String moderatorPass, String viewerPass, Long createTime,
+			                      String createDate, Boolean isBreakout) {
+	  CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(meetingID, externalMeetingID, meetingName, 
+				                                  recorded, voiceBridge, duration, 
+				                                  autoStartRecording, allowStartStopRecording,
+				                                  moderatorPass, viewerPass, createTime, createDate, isBreakout);
+	  CreateMeetingRequest msg = new CreateMeetingRequest(payload);
+	  
+	  Gson gson = new Gson();
+		String json = gson.toJson(msg);
+		log.info("Sending create meeting message to bbb-apps:[{}]", json);
+		sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);			
+	}
+	
+	public void endMeeting(String meetingId) {
+		EndMeetingMessage msg = new EndMeetingMessage(meetingId);
+		String json = MessageToJson.endMeetingMessageToJson(msg);
+		log.info("Sending end meeting message to bbb-apps:[{}]", json);
+		sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);	
+	}
+
+  public void sendKeepAlive(String system, Long timestamp) {
+   	String json = encoder.encodePubSubPingMessage("BbbWeb", System.currentTimeMillis());	
+	sender.send(MessagingConstants.TO_SYSTEM_CHANNEL, json);		
+  }
+	
+  public void send(String channel, String message) {
+		sender.send(channel, message);
+  }
+  
+	public void sendPolls(String meetingId, String title, String question, String questionType, List<String> answers){
+		Gson gson = new Gson();
+
+		HashMap<String, Object> map = new HashMap<String, Object>();
+		map.put("messageId", MessagingConstants.SEND_POLLS_EVENT);
+		map.put("meetingId", meetingId);
+		map.put("title", title);
+		map.put("question", question);
+		map.put("questionType", questionType);
+		map.put("answers", answers);
+		
+		sender.send(MessagingConstants.TO_POLLING_CHANNEL, gson.toJson(map));		
+	}
+	
+	public void setMessageSender(MessageSender sender) {
+		this.sender = sender;
+	}
+	
+  public void setRedisStorageService(RedisStorageService storeService) {
+  	this.storeService = storeService;
+  }
+  
+	public String storeSubscription(String meetingId, String externalMeetingID, String callbackURL){
+		return storeService.storeSubscription(meetingId, externalMeetingID, callbackURL);
+	}
+
+	public boolean removeSubscription(String meetingId, String subscriptionId){
+		return storeService.removeSubscription(meetingId, subscriptionId);
+	}
+
+	public List<Map<String,String>> listSubscriptions(String meetingId){
+		return storeService.listSubscriptions(meetingId);	
+	}	
+
+	public void removeMeeting(String meetingId){
+		storeService.removeMeeting(meetingId);
+	}
+
+	public void sendStunTurnInfo(String meetingId, String internalUserId, Set<StunServer> stuns, Set<TurnEntry> turns) {
+		ArrayList<String> stunsArrayList = new ArrayList<String>();
+		Iterator stunsIter = stuns.iterator();
+
+		while (stunsIter.hasNext()) {
+			StunServer aStun = (StunServer) stunsIter.next();
+			if (aStun != null) {
+				stunsArrayList.add(aStun.url);
+			}
+		}
+
+		ArrayList<Map<String, Object>> turnsArrayList = new ArrayList<Map<String, Object>>();
+		Iterator turnsIter = turns.iterator();
+		while (turnsIter.hasNext()) {
+			TurnEntry te = (TurnEntry) turnsIter.next();
+			if (null != te) {
+				Map<String, Object> map = new HashMap<String, Object>();
+				map.put(Constants.USERNAME, te.username);
+				map.put(Constants.URL, te.url);
+				map.put(Constants.TTL, te.ttl);
+				map.put(Constants.PASSWORD, te.password);
+
+				turnsArrayList.add(map);
+			}
+		}
+
+		SendStunTurnInfoReplyMessage msg = new SendStunTurnInfoReplyMessage(meetingId, internalUserId,
+				stunsArrayList, turnsArrayList);
+
+		sender.send(MessagingConstants.TO_BBB_HTML5_CHANNEL, msg.toJson());
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java
new file mode 100755
index 0000000000..1797a97ff2
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java
@@ -0,0 +1,126 @@
+package org.bigbluebutton.api.messaging;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.Protocol;
+
+public class RedisStorageService {
+	private static Logger log = LoggerFactory.getLogger(RedisStorageService.class);
+	
+	private JedisPool redisPool;
+	private String host;
+	private int port;
+	
+	public void stop() {
+
+	}
+	
+	public void start() {
+		// Set the name of this client to be able to distinguish when doing
+		// CLIENT LIST on redis-cli
+		redisPool = new JedisPool(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, null,
+		        Protocol.DEFAULT_DATABASE, "BbbRed5AppsPub");
+					
+	}
+	
+	public void recordMeetingInfo(String meetingId, Map<String, String> info) {
+		Jedis jedis = redisPool.getResource();
+		try {
+		    for (String key: info.keySet()) {
+				   log.debug("Storing metadata {} = {}", key, info.get(key));
+				}   
+
+		    log.debug("Saving metadata in {}", meetingId);
+			jedis.hmset("meeting:info:" + meetingId, info);
+		} catch (Exception e){
+			log.warn("Cannot record the info meeting:"+meetingId,e);
+		} finally {
+			jedis.close();
+		}		
+	}
+	
+	public void removeMeeting(String meetingId){
+		Jedis jedis = redisPool.getResource();
+		try {
+			jedis.del("meeting-" + meetingId);
+			jedis.srem("meetings", meetingId);
+		} finally {
+			jedis.close();
+		}
+	}
+	
+	public List<Map<String,String>> listSubscriptions(String meetingId){
+		List<Map<String,String>> list = new ArrayList<Map<String,String>>();
+		Jedis jedis = redisPool.getResource();
+		try {
+			List<String> sids = jedis.lrange("meeting:" + meetingId + ":subscriptions", 0 , -1);
+			for(int i=0; i<sids.size(); i++){
+				Map<String,String> props = jedis.hgetAll("meeting:" + meetingId + ":subscription:" + sids.get(i));
+				list.add(props);	
+			}
+				
+		} catch (Exception e){
+			log.warn("Cannot list subscriptions:" + meetingId, e);
+		} finally {
+			jedis.close();
+		}
+
+		return list;	
+	}	
+	
+	public boolean removeSubscription(String meetingId, String subscriptionId){
+		boolean unsubscribed = true;
+		Jedis jedis = redisPool.getResource();
+		try {
+			jedis.hset("meeting:" + meetingId + ":subscription:" + subscriptionId, "active", "false");	
+		} catch (Exception e){
+			log.warn("Cannot rmove subscription:" + meetingId, e);
+			unsubscribed = false;
+		} finally {
+			jedis.close();
+		}
+
+		return unsubscribed; 	
+	}
+	
+	public String storeSubscription(String meetingId, String externalMeetingID, String callbackURL){
+		String sid = "";
+		Jedis jedis = redisPool.getResource();
+		try {
+			sid = Long.toString(jedis.incr("meeting:" + meetingId + ":nextSubscription"));
+
+			HashMap<String,String> props = new HashMap<String,String>();
+			props.put("subscriptionID", sid);
+			props.put("meetingId", meetingId);
+			props.put("externalMeetingID", externalMeetingID);
+			props.put("callbackURL", callbackURL);
+			props.put("active", "true");
+
+			jedis.hmset("meeting:" + meetingId + ":subscription:" + sid, props);
+			jedis.rpush("meeting:" + meetingId + ":subscriptions", sid);
+			
+		} catch (Exception e){
+			log.warn("Cannot store subscription:" + meetingId, e);
+		} finally {
+			jedis.close();
+		}
+
+		return sid; 	
+	}
+	
+	public void setHost(String host){
+		this.host = host;
+	}
+	
+	public void setPort(int port) {
+		this.port = port;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/MessageFromJsonConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/MessageFromJsonConverter.java
new file mode 100755
index 0000000000..80e30baffb
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/MessageFromJsonConverter.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.api.messaging.converters;
+
+import org.bigbluebutton.api.messaging.messages.IMessage;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class MessageFromJsonConverter {
+
+	public IMessage convert(String json) {
+		JsonParser parser = new JsonParser();
+		JsonObject obj = (JsonObject) parser.parse(json);
+		if (obj.has("header") && obj.has("payload")) {
+			
+		}
+		
+		return null;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/CreateMeetingMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/CreateMeetingMessage.java
new file mode 100755
index 0000000000..7c747034e5
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/CreateMeetingMessage.java
@@ -0,0 +1,39 @@
+package org.bigbluebutton.api.messaging.converters.messages;
+
+
+public class CreateMeetingMessage {
+	public static final String CREATE_MEETING_REQUEST_EVENT  = "create_meeting_request";
+	public static final String VERSION = "0.0.1";
+	
+	public final String id;
+	public final String externalId;
+	public final String name;
+	public final Boolean record;
+	public final String voiceBridge;
+	public final Long duration;
+	public boolean autoStartRecording;
+	public boolean allowStartStopRecording;
+	public final String moderatorPass;
+	public final String viewerPass;
+	public final Long createTime;
+	public final String createDate;
+	
+	public CreateMeetingMessage(String id, String externalId, String name, Boolean record, 
+						String voiceBridge, Long duration, 
+						Boolean autoStartRecording, Boolean allowStartStopRecording,
+						String moderatorPass, String viewerPass, Long createTime,
+						String createDate) {
+		this.id = id;
+		this.externalId = externalId;
+		this.name = name;
+		this.record = record;
+		this.voiceBridge = voiceBridge;
+		this.duration = duration;
+		this.autoStartRecording = autoStartRecording;	
+		this.allowStartStopRecording = allowStartStopRecording;
+		this.moderatorPass = moderatorPass;
+		this.viewerPass = viewerPass;
+		this.createTime = createTime;
+		this.createDate = createDate;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DestroyMeetingMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DestroyMeetingMessage.java
new file mode 100755
index 0000000000..83f2ad58e7
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DestroyMeetingMessage.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.api.messaging.converters.messages;
+
+public class DestroyMeetingMessage {
+	public static final String DESTROY_MEETING_REQUEST_EVENT  = "destroy_meeting_request_event";
+	public static final String VERSION = "0.0.1";
+	
+	public final String meetingId;
+	
+	public DestroyMeetingMessage(String meetingId) {
+		this.meetingId = meetingId;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/EndMeetingMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/EndMeetingMessage.java
new file mode 100755
index 0000000000..92d6493376
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/EndMeetingMessage.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.api.messaging.converters.messages;
+
+public class EndMeetingMessage {
+	public static final String END_MEETING_REQUEST_EVENT  = "end_meeting_request_event";
+	public static final String VERSION = "0.0.1";
+
+	public final String meetingId;
+	
+	public EndMeetingMessage(String meetingId) {
+		this.meetingId = meetingId;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/KeepAliveMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/KeepAliveMessage.java
new file mode 100755
index 0000000000..38bb71072e
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/KeepAliveMessage.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.api.messaging.converters.messages;
+
+public class KeepAliveMessage {
+	public static final String KEEP_ALIVE_REQUEST                 = "keep_alive_request";
+	public static final String VERSION = "0.0.1";
+	
+	public final String keepAliveId;
+	
+	public KeepAliveMessage(String keepAliveId) {
+		this.keepAliveId = keepAliveId;	
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/RegisterUserMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/RegisterUserMessage.java
new file mode 100755
index 0000000000..c1bfa9c085
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/converters/messages/RegisterUserMessage.java
@@ -0,0 +1,24 @@
+package org.bigbluebutton.api.messaging.converters.messages;
+
+public class RegisterUserMessage {
+	public static final String REGISTER_USER                 = "register_user_request";
+	public final String VERSION = "0.0.1";
+	
+	public final String meetingID;
+	public final String internalUserId;
+	public final String fullname;
+	public final String role;
+	public final String externUserID;
+	public final String authToken;
+	public final String avatarURL;
+	
+	public RegisterUserMessage(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, String avatarURL) {
+		this.meetingID = meetingID;
+		this.internalUserId = internalUserId;
+		this.fullname = fullname;
+		this.role = role;
+		this.externUserID = externUserID;	
+		this.authToken = authToken;
+		this.avatarURL = avatarURL;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateBreakoutRoom.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateBreakoutRoom.java
new file mode 100755
index 0000000000..d9dcb3deba
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateBreakoutRoom.java
@@ -0,0 +1,27 @@
+package org.bigbluebutton.api.messaging.messages;
+
+
+public class CreateBreakoutRoom implements IMessage {
+
+  public final String breakoutId;
+  public final String parentId;      // The main meeting internal id
+  public final String name;               // The name of the breakout room
+  public final String voiceConfId;        // The voice conference id
+  public final String viewerPassword;
+  public final String moderatorPassword;
+  public final Integer durationInMinutes; // The duration of the breakout room
+  public final String defaultPresentationURL;
+	
+	public CreateBreakoutRoom(String breakoutId, String parentId, String name, 
+      String voiceConfId, String viewerPassword, String moderatorPassword, 
+      Integer duration, String defaultPresentationURL) {
+	  this.breakoutId = breakoutId;
+    this.parentId = parentId;
+    this.name = name;
+    this.voiceConfId = voiceConfId;
+    this.viewerPassword = viewerPassword;
+    this.moderatorPassword = moderatorPassword;
+    this.durationInMinutes = duration;
+    this.defaultPresentationURL = defaultPresentationURL;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateMeeting.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateMeeting.java
new file mode 100755
index 0000000000..9e91d559e1
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/CreateMeeting.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.api.messaging.messages;
+
+import org.bigbluebutton.api.domain.Meeting;
+
+public class CreateMeeting implements IMessage {
+
+	public final Meeting meeting;
+	
+	public CreateMeeting(Meeting meeting) {
+		this.meeting = meeting;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndBreakoutRoom.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndBreakoutRoom.java
new file mode 100755
index 0000000000..dc5c2b3683
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndBreakoutRoom.java
@@ -0,0 +1,9 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class EndBreakoutRoom implements IMessage {
+  public final String breakoutId;
+
+  public EndBreakoutRoom(String breakoutId) {
+    this.breakoutId = breakoutId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndMeeting.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndMeeting.java
new file mode 100755
index 0000000000..6d4430c72e
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/EndMeeting.java
@@ -0,0 +1,10 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class EndMeeting implements IMessage {
+
+	public final String meetingId;
+	
+	public EndMeeting(String meetingId) {
+		this.meetingId = meetingId;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/IMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/IMessage.java
new file mode 100755
index 0000000000..8d40b70fbc
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/IMessage.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public interface IMessage {
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/KeepAliveReply.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/KeepAliveReply.java
new file mode 100755
index 0000000000..d999e45d68
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/KeepAliveReply.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class KeepAliveReply implements IMessage {
+	
+  public final String system;
+  public final Long timestamp;
+  
+  public KeepAliveReply(String system, Long timestamp) {
+  	this.system = system;
+  	this.timestamp = timestamp;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingDestroyed.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingDestroyed.java
new file mode 100755
index 0000000000..f6d1ff49f3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingDestroyed.java
@@ -0,0 +1,9 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class MeetingDestroyed implements IMessage {
+  public final String meetingId;
+  
+  public MeetingDestroyed(String meetingId) {
+  	this.meetingId = meetingId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingEnded.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingEnded.java
new file mode 100755
index 0000000000..dcc4b9da2a
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingEnded.java
@@ -0,0 +1,9 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class MeetingEnded implements IMessage {
+  public final String meetingId;
+  
+  public MeetingEnded(String meetingId) {
+  	this.meetingId = meetingId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingStarted.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingStarted.java
new file mode 100755
index 0000000000..3f28136534
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/MeetingStarted.java
@@ -0,0 +1,9 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class MeetingStarted implements IMessage {
+  public final String meetingId;
+  
+  public MeetingStarted(String meetingId) {
+  	this.meetingId = meetingId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RegisterUser.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RegisterUser.java
new file mode 100755
index 0000000000..3c3451297c
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RegisterUser.java
@@ -0,0 +1,22 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class RegisterUser implements IMessage {
+
+	public final String meetingID;
+	public final String internalUserId;
+	public final String fullname;
+	public final String role;
+	public final String externUserID;
+	public final String authToken;
+	public final String avatarURL;
+	
+	public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, String avatarURL) {
+		this.meetingID = meetingID;
+		this.internalUserId = internalUserId;
+		this.fullname = fullname;
+		this.role = role;
+		this.externUserID = externUserID;
+		this.authToken = authToken;		
+		this.avatarURL = avatarURL;		
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RemoveExpiredMeetings.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RemoveExpiredMeetings.java
new file mode 100755
index 0000000000..6282e017ed
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/RemoveExpiredMeetings.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class RemoveExpiredMeetings implements IMessage {
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/StunTurnInfoRequested.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/StunTurnInfoRequested.java
new file mode 100755
index 0000000000..d1e4da7ae9
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/StunTurnInfoRequested.java
@@ -0,0 +1,11 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class StunTurnInfoRequested implements IMessage {
+	public final String meetingId;
+	public final String internalUserId;
+
+	public StunTurnInfoRequested (String meetingId, String internalUserId) {
+		this.meetingId = meetingId;
+		this.internalUserId = internalUserId;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoined.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoined.java
new file mode 100755
index 0000000000..cad1acb397
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoined.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserJoined implements IMessage {
+  public final String meetingId;
+  public final String userId;
+  public final String externalUserId;
+  public final String name;
+  public final String role;
+  public final String avatarURL;
+  
+  public UserJoined(String meetingId, String userId, String externalUserId, String name, String role, String avatarURL) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  	this.externalUserId = externalUserId;
+  	this.name = name;
+  	this.role = role;
+  	this.avatarURL = avatarURL;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoinedVoice.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoinedVoice.java
new file mode 100755
index 0000000000..95e9e76ee0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserJoinedVoice.java
@@ -0,0 +1,11 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserJoinedVoice implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  
+  public UserJoinedVoice(String meetingId, String userId) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeft.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeft.java
new file mode 100755
index 0000000000..401f77b3f4
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeft.java
@@ -0,0 +1,11 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserLeft implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  
+  public UserLeft(String meetingId, String userId) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeftVoice.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeftVoice.java
new file mode 100755
index 0000000000..b4060b2731
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserLeftVoice.java
@@ -0,0 +1,11 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserLeftVoice implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  
+  public UserLeftVoice(String meetingId, String userId) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserListeningOnly.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserListeningOnly.java
new file mode 100755
index 0000000000..fde4d2a28e
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserListeningOnly.java
@@ -0,0 +1,13 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserListeningOnly implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  public final Boolean listenOnly;
+  
+  public UserListeningOnly(String meetingId, String userId, Boolean listenOnly) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  	this.listenOnly = listenOnly;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserSharedWebcam.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserSharedWebcam.java
new file mode 100755
index 0000000000..826255a252
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserSharedWebcam.java
@@ -0,0 +1,13 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserSharedWebcam implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  public final String stream;
+  
+  public UserSharedWebcam(String meetingId, String userId, String stream) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  	this.stream = stream;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserStatusChanged.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserStatusChanged.java
new file mode 100755
index 0000000000..2b1e17e35f
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserStatusChanged.java
@@ -0,0 +1,15 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserStatusChanged implements IMessage {
+  public final String meetingId;
+  public final String userId;
+  public final String status;
+  public final String value;
+  
+  public UserStatusChanged(String meetingId, String userId, String status, String value) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  	this.status = status;
+  	this.value = value;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserUnsharedWebcam.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserUnsharedWebcam.java
new file mode 100755
index 0000000000..3f8c5622af
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/messaging/messages/UserUnsharedWebcam.java
@@ -0,0 +1,13 @@
+package org.bigbluebutton.api.messaging.messages;
+
+public class UserUnsharedWebcam implements IMessage {
+  public final String userId;
+  public final String meetingId;
+  public final String stream;
+  
+  public UserUnsharedWebcam(String meetingId, String userId, String stream) {
+  	this.meetingId = meetingId;
+  	this.userId = userId;
+  	this.stream = stream;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/api/responses/InvalidResponse.java b/bbb-web-api/src/main/java/org/bigbluebutton/api/responses/InvalidResponse.java
new file mode 100755
index 0000000000..f01950aa2b
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/api/responses/InvalidResponse.java
@@ -0,0 +1,13 @@
+package org.bigbluebutton.api.responses;
+
+public class InvalidResponse {
+    public final String returnCode;
+    public final String messageKey;
+    public final String message;
+
+    public InvalidResponse(String returnCode, String messageKey, String message) {
+        this.returnCode = returnCode;
+        this.messageKey = messageKey;
+        this.message = message;
+    }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/BbbAppsIsAliveMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/BbbAppsIsAliveMessage.java
new file mode 100755
index 0000000000..5123ca96a3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/BbbAppsIsAliveMessage.java
@@ -0,0 +1,53 @@
+package org.bigbluebutton.common.messages;
+
+import java.util.HashMap;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class BbbAppsIsAliveMessage {
+	public static final String BBB_APPS_IS_ALIVE = "bbb_apps_is_alive_message";
+	public static final String VERSION = "0.0.1";
+
+	public static final String TIMESTAMP = "timestamp";	
+	public static final String STARTED_ON = "started_on";
+	
+	public final Long timestamp;
+	public final Long startedOn;
+
+	public BbbAppsIsAliveMessage(Long startedOn, Long timestamp) {
+		this.startedOn = startedOn;
+		this.timestamp = timestamp;
+	}
+
+	public String toJson() {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(TIMESTAMP, timestamp);
+		payload.put(STARTED_ON, timestamp);
+		
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(BBB_APPS_IS_ALIVE, VERSION, null);
+		return MessageBuilder.buildJson(header, payload);
+	}
+
+	public static BbbAppsIsAliveMessage fromJson(String message) {
+		JsonParser parser = new JsonParser();
+		JsonObject obj = (JsonObject) parser.parse(message);
+		if (obj.has("header") && obj.has("payload")) {
+			JsonObject header = (JsonObject) obj.get("header");
+			JsonObject payload = (JsonObject) obj.get("payload");
+
+			if (header.has("name")) {
+				String messageName = header.get("name").getAsString();
+				if (BBB_APPS_IS_ALIVE.equals(messageName)) {
+
+					if (payload.has(TIMESTAMP) && payload.has(STARTED_ON)) {
+						Long timestamp = payload.get(TIMESTAMP).getAsLong();
+						Long startedOn = payload.get(STARTED_ON).getAsLong();
+						return new BbbAppsIsAliveMessage(startedOn, timestamp);
+					}
+				}
+			}
+		}
+		return null;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/MessageBuilder.java b/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/MessageBuilder.java
new file mode 100755
index 0000000000..074d86f2fd
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/common/messages/MessageBuilder.java
@@ -0,0 +1,41 @@
+package org.bigbluebutton.common.messages;
+
+import java.util.concurrent.TimeUnit;
+
+import com.google.gson.Gson;
+
+public class MessageBuilder {
+  public final static String VERSION = "version";
+  public static final String NAME                            = "name";
+  public static final String HEADER                          = "header";
+  public static final String PAYLOAD                         = "payload";
+  public static final String TIMESTAMP                       = "timestamp";
+  public static final String REPLY_TO                        = "reply_to";
+  
+  public static long generateTimestamp() {
+    return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+  }
+  
+  public static java.util.HashMap<String, Object> buildHeader(String name, String version, String replyTo) {
+  	java.util.HashMap<String, Object> header = new java.util.HashMap<String, Object>();
+    header.put(NAME, name);
+    header.put(VERSION, version);
+    header.put(TIMESTAMP, generateTimestamp());
+    if (replyTo != null && replyTo != "")
+      header.put(REPLY_TO, replyTo);
+    
+    return header;
+  }
+  
+  
+  public static String buildJson(java.util.HashMap<String, Object> header, 
+  		java.util.HashMap<String, Object> payload) {
+    
+  	java.util.HashMap<String, java.util.HashMap<String, Object>> message = new java.util.HashMap<String, java.util.HashMap<String, Object>>();
+    message.put(HEADER, header);
+    message.put(PAYLOAD, payload);
+    
+    Gson gson = new Gson();
+    return gson.toJson(message);
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java
new file mode 100755
index 0000000000..53aa6b3c0c
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java
@@ -0,0 +1,39 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public class ConversionMessageConstants {
+	private ConversionMessageConstants() {}
+	
+	public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS";
+	public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED";
+	public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT";
+	public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT";
+	public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED";
+	public static final String PAGE_COUNT_EXCEEDED_KEY = "PAGE_COUNT_EXCEEDED";	
+	public static final String GENERATED_SLIDE_KEY = "GENERATED_SLIDE";
+	public static final String GENERATING_THUMBNAIL_KEY = "GENERATING_THUMBNAIL";
+	public static final String GENERATED_THUMBNAIL_KEY = "GENERATED_THUMBNAIL";
+	public static final String GENERATING_TEXTFILES_KEY = "GENERATING_TEXTFILES";
+	public static final String GENERATED_TEXTFILES_KEY = "GENERATED_TEXTFILES";
+	public static final String GENERATING_SVGIMAGES_KEY = "GENERATING_SVGIMAGES";
+	public static final String GENERATED_SVGIMAGES_KEY = "GENERATED_SVGIMAGES";
+	public static final String CONVERSION_COMPLETED_KEY = "CONVERSION_COMPLETED";
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionUpdateMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionUpdateMessage.java
new file mode 100755
index 0000000000..126d1db12d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ConversionUpdateMessage.java
@@ -0,0 +1,111 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConversionUpdateMessage {
+	private Map<String, Object> message = new HashMap<String, Object>();
+	
+	private ConversionUpdateMessage(MessageBuilder builder) {
+		message = builder.message;
+	}
+	
+	public Map<String, Object> getMessage() {
+		return message;
+	}
+	
+	public static class MessageBuilder {
+		private Map<String, Object> message = new HashMap<String, Object>();
+		
+		public MessageBuilder(UploadedPresentation pres) {
+			message.put("conference", pres.getMeetingId());
+			message.put("room", pres.getMeetingId());
+			message.put("returnCode", "CONVERT");
+			message.put("presentationName", pres.getId());
+			message.put("presentationId", pres.getId());
+			message.put("filename", pres.getName());
+    	}
+		
+		public MessageBuilder entry(String key, Object value) {
+			message.put(key, value);
+			return this;
+		}
+		
+		public MessageBuilder messageKey(String messageKey) {
+			message.put("messageKey", messageKey);
+			return this;
+		}
+		
+		public MessageBuilder pagesCompleted(int pagesCompleted) {
+			message.put("pagesCompleted", pagesCompleted);
+			return this;
+		}
+		
+		public MessageBuilder numberOfPages(int numberOfPages) {
+			message.put("numberOfPages", numberOfPages);
+			return this;
+		}
+		
+		public MessageBuilder maxNumberPages(int maxNumberPages) {
+			message.put("maxNumberPages", maxNumberPages);
+			return this;
+		}
+		
+		public MessageBuilder slidesInfo(String slidesInfo) {
+			message.put("slidesInfo", slidesInfo);
+			return this;
+		} 
+		
+		public MessageBuilder presBaseUrl(UploadedPresentation pres) {
+			message.put("presentationBaseUrl", generateBasePresUrl(pres));
+			return this;
+		} 
+				
+		public ConversionUpdateMessage build() {
+			return new ConversionUpdateMessage(this);
+		}
+		
+		public MessageBuilder generatePages(UploadedPresentation pres) {
+			String basePresUrl = generateBasePresUrl(pres);
+			ArrayList<Map<String, String>> pages = new ArrayList<Map<String, String>>();
+			
+			for (int i = 1; i <= pres.getNumberOfPages(); i++) {
+				Map<String, String> page = new HashMap<String, String>();
+				page.put("num", new Integer(i).toString());
+				page.put("thumb", basePresUrl + "/thumbnail/" + i);
+				page.put("swf", basePresUrl + "/slide/" + i);
+				page.put("text", basePresUrl + "/textfiles/" + i);
+				
+				pages.add(page);
+			}
+			
+			message.put("pages", pages);
+			
+			return this;
+		}
+		
+		private String generateBasePresUrl(UploadedPresentation pres) {
+			return pres.getBaseUrl() + "/" + pres.getMeetingId() + "/" + pres.getMeetingId() + "/" + pres.getId();
+		}
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionService.java
new file mode 100755
index 0000000000..ea21767161
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionService.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public interface DocumentConversionService {
+	void processDocument(UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java
new file mode 100755
index 0000000000..d20acb0cb0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java
@@ -0,0 +1,82 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * <p>
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ * <p>
+ * This program 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.0 of the License, or (at your option) any later
+ * version.
+ * <p>
+ * 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.
+ * <p>
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.bigbluebutton.presentation;
+
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.bigbluebutton.presentation.imp.ImageToSwfSlidesGenerationService;
+import org.bigbluebutton.presentation.imp.OfficeToPdfConversionService;
+import org.bigbluebutton.presentation.imp.PdfToSwfSlidesGenerationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DocumentConversionServiceImp implements DocumentConversionService {
+    private static Logger log = LoggerFactory.getLogger(DocumentConversionServiceImp.class);
+
+    private MessagingService messagingService;
+    private OfficeToPdfConversionService officeToPdfConversionService;
+    private PdfToSwfSlidesGenerationService pdfToSwfSlidesGenerationService;
+    private ImageToSwfSlidesGenerationService imageToSwfSlidesGenerationService;
+
+    public void processDocument(UploadedPresentation pres) {
+        SupportedDocumentFilter sdf = new SupportedDocumentFilter(messagingService);
+        log.info("Start presentation conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=" + pres.getName());
+
+        if (sdf.isSupported(pres)) {
+            String fileType = pres.getFileType();
+
+            if (SupportedFileTypes.isOfficeFile(fileType)) {
+                officeToPdfConversionService.convertOfficeToPdf(pres);
+                OfficeToPdfConversionSuccessFilter ocsf = new OfficeToPdfConversionSuccessFilter(messagingService);
+                if (ocsf.didConversionSucceed(pres)) {
+                    // Successfully converted to pdf. Call the process again, this time it should be handled by
+                    // the PDF conversion service.
+                    processDocument(pres);
+                }
+            } else if (SupportedFileTypes.isPdfFile(fileType)) {
+                pdfToSwfSlidesGenerationService.generateSlides(pres);
+            } else if (SupportedFileTypes.isImageFile(fileType)) {
+                imageToSwfSlidesGenerationService.generateSlides(pres);
+            } else {
+
+            }
+
+        } else {
+            // TODO: error log
+        }
+
+        log.info("End presentation conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=" + pres.getName());
+
+    }
+
+    public void setMessagingService(MessagingService m) {
+        messagingService = m;
+    }
+
+    public void setOfficeToPdfConversionService(OfficeToPdfConversionService s) {
+        officeToPdfConversionService = s;
+    }
+
+    public void setPdfToSwfSlidesGenerationService(PdfToSwfSlidesGenerationService s) {
+        pdfToSwfSlidesGenerationService = s;
+    }
+
+    public void setImageToSwfSlidesGenerationService(ImageToSwfSlidesGenerationService s) {
+        imageToSwfSlidesGenerationService = s;
+    }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/FileTypeConstants.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/FileTypeConstants.java
new file mode 100755
index 0000000000..d987cb23b0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/FileTypeConstants.java
@@ -0,0 +1,45 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public final class FileTypeConstants {
+	private FileTypeConstants() {} // Prevent instantiation
+	
+	/* OFFICE FILE */	
+	public static final String XLS = "xls";
+	public static final String XLSX = "xlsx";
+	public static final String DOC = "doc";
+	public static final String DOCX = "docx";
+	public static final String PPT = "ppt";
+	public static final String PPTX = "pptx";
+	public static final String ODT = "odt";
+	public static final String RTF = "rtf";
+	public static final String TXT = "txt";
+	public static final String ODS = "ods";
+	public static final String ODP = "odp";
+	
+	public static final String AVI = "avi";
+	public static final String MPG = "mpg";
+	public static final String MP3 = "mp3";
+	public static final String PDF = "pdf";
+	public static final String JPG = "jpg";
+	public static final String JPEG = "jpeg";
+	public static final String PNG = "png";
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/GeneratedSlidesInfoHelper.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/GeneratedSlidesInfoHelper.java
new file mode 100755
index 0000000000..9cf16a1fcf
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/GeneratedSlidesInfoHelper.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public interface GeneratedSlidesInfoHelper {
+	String generateUploadedPresentationInfo(UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ImageToSwfSlide.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ImageToSwfSlide.java
new file mode 100755
index 0000000000..b39875d4dc
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ImageToSwfSlide.java
@@ -0,0 +1,94 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ImageToSwfSlide {
+  private static Logger log = LoggerFactory.getLogger(ImageToSwfSlide.class);
+
+  private UploadedPresentation pres;
+  private int page;
+
+  private PageConverter imageToSwfConverter;
+  private String BLANK_SLIDE;
+
+  private boolean done = false;
+  private File slide;
+
+  public ImageToSwfSlide(UploadedPresentation pres, int page) {
+    this.pres = pres;
+    this.page = page;
+  }
+
+  public ImageToSwfSlide createSlide() {		
+    File presentationFile = pres.getUploadedFile();
+    slide = new File(presentationFile.getParent() + File.separatorChar + "slide-" + page + ".swf");
+    log.debug("Creating slide " + slide.getAbsolutePath());
+    imageToSwfConverter.convert(presentationFile, slide, page, pres);
+
+    // If all fails, generate a blank slide.
+    if (!slide.exists()) {
+      log.warn("Creating blank slide for " + slide.getAbsolutePath());
+      generateBlankSlide();
+    }
+
+    done = true;
+
+    return this;
+  }
+
+  public void generateBlankSlide() {
+    if (BLANK_SLIDE != null) {
+      copyBlankSlide(slide);
+    } else {
+      log.error("Blank slide has not been set");
+    }		
+  }
+
+  private void copyBlankSlide(File slide) {
+    try {
+      FileUtils.copyFile(new File(BLANK_SLIDE), slide);
+    } catch (IOException e) {
+      log.error("IOException while copying blank slide.");
+    }
+  }
+
+  public void setPageConverter(PageConverter converter) {
+    this.imageToSwfConverter = converter;
+  }
+
+  public void setBlankSlide(String blankSlide) {
+    this.BLANK_SLIDE = blankSlide;
+  }
+
+  public boolean isDone() {
+    return done;
+  }
+
+  public int getPageNumber() {
+    return page;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java
new file mode 100755
index 0000000000..6564edf6fa
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java
@@ -0,0 +1,78 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bigbluebutton.api.messaging.MessagingConstants;
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+public class OfficeToPdfConversionSuccessFilter {
+	private static Logger log = LoggerFactory.getLogger(OfficeToPdfConversionSuccessFilter.class);
+
+	private final MessagingService messagingService;
+	
+	public OfficeToPdfConversionSuccessFilter(MessagingService m) {
+		messagingService = m;
+	}
+	
+	public boolean didConversionSucceed(UploadedPresentation pres) {
+		notifyProgressListener(pres);
+		return pres.isLastStepSuccessful();
+	}
+
+	private void notifyProgressListener(UploadedPresentation pres) {
+		Map<String, Object> msg = new HashMap<String, Object>();
+		msg.put("conference", pres.getMeetingId());
+		msg.put("room", pres.getMeetingId());
+		msg.put("returnCode", "CONVERT");
+		msg.put("presentationId", pres.getId());
+		msg.put("presentationName", pres.getId());
+		msg.put("filename", pres.getName());
+		
+		if (pres.isLastStepSuccessful()) {
+			log.info("Notifying of OFFICE_DOC_CONVERSION_SUCCESS for " + pres.getUploadedFile().getAbsolutePath());
+			msg.put("message", "Office document successfully converted.");
+			msg.put("messageKey", "OFFICE_DOC_CONVERSION_SUCCESS");
+		} else {
+			log.info("Notifying of OFFICE_DOC_CONVERSION_FAILED for " + pres.getUploadedFile().getAbsolutePath());
+			msg.put("message", "Failed to convert Office document.");
+			msg.put("messageKey", "OFFICE_DOC_CONVERSION_FAILED");
+		}
+		
+		sendNotification(msg);
+	}
+	
+	private void sendNotification(Map<String, Object> msg) {
+		if (messagingService != null){
+			Gson gson = new Gson();
+			String updateMsg = gson.toJson(msg);
+			log.debug("sending: " + updateMsg);
+			messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg);
+		} else {
+			log.warn("MessagingService has not been set!.");
+		}
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Page.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Page.java
new file mode 100755
index 0000000000..441fa928fe
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Page.java
@@ -0,0 +1,16 @@
+package org.bigbluebutton.presentation;
+
+public class Page {
+
+	private final int num;
+	
+	public Page(int num) {
+	  this.num = num;	
+	}
+	
+	public int getNum() {
+		return num;
+	}
+	
+	 
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageAnalyser.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageAnalyser.java
new file mode 100755
index 0000000000..d673427409
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageAnalyser.java
@@ -0,0 +1,32 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public interface PageAnalyser {
+  /**
+   * 
+   * @param output
+   *          a {@link File} to analyse
+   * @return true if the file has been parsed without any error
+   */
+  public boolean analyse(File output);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageConverter.java
new file mode 100755
index 0000000000..c8120a49db
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageConverter.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public interface PageConverter {
+	public boolean convert(File presentation, File output, int page, UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageCounter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageCounter.java
new file mode 100755
index 0000000000..24b762c85d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageCounter.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public interface PageCounter {
+	public int countNumberOfPages(File presentation);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageExtractor.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageExtractor.java
new file mode 100755
index 0000000000..9ec8b45fc7
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PageExtractor.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public interface PageExtractor {
+	public boolean extractPage(File presentationFile, File output, int page);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java
new file mode 100755
index 0000000000..7abc436273
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java
@@ -0,0 +1,148 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.bigbluebutton.presentation.imp.PdfPageToImageConversionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+public class PdfToSwfSlide {
+  private static Logger log = LoggerFactory.getLogger(PdfToSwfSlide.class);
+
+  private UploadedPresentation pres;
+  private int page;
+  private PageConverter pdfToSwfConverter;
+  private PdfPageToImageConversionService imageConvertService;
+  private String BLANK_SLIDE;
+  private int MAX_SWF_FILE_SIZE;
+
+  private volatile boolean done = false;
+  private File slide;
+
+  public PdfToSwfSlide(UploadedPresentation pres, int page) {
+    this.pres = pres;
+    this.page = page;
+  }
+
+  public PdfToSwfSlide createSlide() {		
+    File presentationFile = pres.getUploadedFile();
+    slide = new File(presentationFile.getParent() + File.separatorChar + "slide-" + page + ".swf");
+    if (! pdfToSwfConverter.convert(presentationFile, slide, page, pres)) {
+      Map<String, Object> logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+      logData.put("size(KB)", slide.length()/1024);
+
+      Gson gson = new Gson();
+      String logStr =  gson.toJson(logData);
+
+      log.warn("Failed to convert slide: data={}", logStr);
+
+      imageConvertService.convertPageAsAnImage(presentationFile, slide, page, pres);
+    } 
+
+    // If all fails, generate a blank slide.
+    if (!slide.exists()) {
+      log.warn("Failed to create slide. Creating blank slide for " + slide.getAbsolutePath());
+      generateBlankSlide();
+    }
+
+    done = true;
+
+    return this;
+  }
+
+  private boolean slideMayHaveTooManyObjects(File slide) {
+    // If the resulting swf file is greater than 500K, it probably contains a lot of objects
+    // that it becomes very slow to render on the client. Take an image snapshot instead and
+    // use it to generate the SWF file. (ralam Sept 2, 2009)
+    return slide.length() > MAX_SWF_FILE_SIZE;
+  }
+
+  public void generateBlankSlide() {
+    if (BLANK_SLIDE != null) {
+      Map<String, Object> logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+
+      Gson gson = new Gson();
+      String logStr =  gson.toJson(logData);
+
+      log.warn("Creating blank slide: data={}", logStr);
+
+      copyBlankSlide(slide);
+    } else {
+      Map<String, Object> logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+
+      Gson gson = new Gson();
+      String logStr =  gson.toJson(logData);
+
+      log.warn("Failed to create blank slide: data={}", logStr);
+    }		
+  }
+
+  private void copyBlankSlide(File slide) {
+    try {
+      FileUtils.copyFile(new File(BLANK_SLIDE), slide);
+    } catch (IOException e) {
+      log.error("IOException while copying blank slide.");
+    }
+  }
+
+  public void setPageConverter(PageConverter converter) {
+    this.pdfToSwfConverter = converter;
+  }
+
+  public void setPdfPageToImageConversionService(PdfPageToImageConversionService service) {
+    this.imageConvertService = service;
+  }
+
+  public void setBlankSlide(String blankSlide) {
+    this.BLANK_SLIDE = blankSlide;
+  }
+
+  public void setMaxSwfFileSize(int size) {
+    this.MAX_SWF_FILE_SIZE = size;
+  }
+
+  public boolean isDone() {
+    return done;
+  }
+
+  public int getPageNumber() {
+    return page;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java
new file mode 100755
index 0000000000..6a9b9d5e3d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java
@@ -0,0 +1,169 @@
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Timer;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PresentationUrlDownloadService {
+	private static Logger log = LoggerFactory.getLogger(PresentationUrlDownloadService.class);
+	
+	private final int maxRedirects = 5;
+	private DocumentConversionService documentConversionService;
+	private String presentationBaseURL;
+	private String presentationDir;
+	
+  public void processUploadedPresentation(UploadedPresentation uploadedPres) {
+    documentConversionService.processDocument(uploadedPres);
+  }
+  
+  public void processUploadedFile(String meetingId, String presId, String filename, File presFile) {
+    UploadedPresentation uploadedPres = new UploadedPresentation(meetingId, presId, filename, presentationBaseURL);
+    uploadedPres.setUploadedFile(presFile);
+    processUploadedPresentation(uploadedPres);
+  }
+  
+	public void downloadAndProcessDocument(String address, String meetingId) {
+	    log.debug("downloadAndProcessDocument [pres=" + address + ", meeting=" + meetingId + "]");
+	    
+	    String presFilename = address.substring(address.lastIndexOf('/') + 1);
+	    log.debug("downloadAndProcessDocument [filename=" + presFilename + "]");
+	    String filenameExt = getFilenameExt(presFilename);
+
+	    String presId = generatePresentationId(presFilename);
+	    File uploadDir = createPresentationDirectory(meetingId, presentationDir, presId);
+	    if (uploadDir != null) {
+	      String newFilename = createNewFilename(presId, filenameExt);
+	      String newFilePath = uploadDir.getAbsolutePath() + File.separatorChar + newFilename;
+	        
+	      if (savePresentation(meetingId, newFilePath, address)) {
+	        File pres = new File(newFilePath);
+	        processUploadedFile(meetingId, presId, presFilename, pres);
+	      } else {
+	        log.error("Failed to download presentation=[" + address + "], meeting=[" + meetingId + "]");
+	      }
+	    } 
+	  }
+	 
+	public String generatePresentationId(String name) {
+		long timestamp = System.currentTimeMillis();		
+		return DigestUtils.shaHex(name) + "-" + timestamp;
+	}
+	
+	public String getFilenameExt(String filename) {
+		return filename.substring(filename.lastIndexOf("."));
+	}
+	
+	public String createNewFilename(String presId, String fileExt) {
+		return presId + fileExt;
+	}
+	
+	public File createPresentationDirectory(String meetingId, String presentationDir, String presentationId) {
+		String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId;
+		String presPath = meetingPath + File.separatorChar + presentationId;
+		File dir = new File(presPath);
+		log.debug("Creating dir [{}]", presPath);
+		if (dir.mkdirs()) {
+			return dir;
+		}
+		return null;
+	}
+	
+	private String followRedirect(String meetingId, String redirectUrl, 
+			int redirectCount, String origUrl) {
+
+		if (redirectCount > maxRedirects) {
+			log.error("Max redirect reached for meeting=[{}] with url=[{}]", meetingId, origUrl);
+			return null;
+		}
+		
+		URL presUrl;
+		try {
+     	presUrl = new URL(redirectUrl);
+    } catch (MalformedURLException e) {
+    	log.error("Malformed url=[{}] for meeting=[{}]", redirectUrl, meetingId);
+	    return null;
+    }			
+
+		HttpURLConnection conn;
+    try {
+	    conn = (HttpURLConnection) presUrl.openConnection();
+			conn.setReadTimeout(5000);
+			conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
+			conn.addRequestProperty("User-Agent", "Mozilla");
+			 
+			// normally, 3xx is redirect
+			int status = conn.getResponseCode();
+			if (status != HttpURLConnection.HTTP_OK) {
+				if (status == HttpURLConnection.HTTP_MOVED_TEMP
+					|| status == HttpURLConnection.HTTP_MOVED_PERM
+					|| status == HttpURLConnection.HTTP_SEE_OTHER) {
+					String newUrl = conn.getHeaderField("Location");
+					return followRedirect(meetingId, newUrl, redirectCount + 1, origUrl);
+				} else {
+					log.error("Invalid HTTP response=[{}] for url=[{}] with meeting[{}]", status, redirectUrl, meetingId);
+					return null;
+				}					
+			} else {
+				return redirectUrl;
+			}			
+    } catch (IOException e) {
+    	log.error("IOException for url=[{}] with meeting[{}]", redirectUrl, meetingId);
+	    return null;
+    }
+	}
+	
+	public boolean savePresentation(final String meetingId, final String filename, 
+			final String urlString) {
+		
+		String finalUrl = followRedirect(meetingId, urlString, 0, urlString);
+		
+		if (finalUrl == null) return false;
+		
+		boolean success = false;
+		GetMethod method = new GetMethod(finalUrl);
+		HttpClient client = new HttpClient();
+		try {
+		    int statusCode = client.executeMethod(method);
+		    if (statusCode == HttpStatus.SC_OK) {     
+		    	FileUtils.copyInputStreamToFile(
+		        method.getResponseBodyAsStream(), new File(filename));
+		    	log.info("Downloaded presentation at [{}]", finalUrl);
+		    	success = true;
+		    }
+		} catch (HttpException e) {
+			log.error("HttpException while downloading presentation at [{}]", finalUrl);
+		} catch (IOException e) {
+			log.error("IOException while downloading presentation at [{}]", finalUrl);
+		} finally {
+			method.releaseConnection();
+		}
+		
+		return success;
+	}
+	
+	public void setPresentationDir(String presDir) {
+	  presentationDir = presDir;
+	}
+	
+	public void setPresentationBaseURL(String presentationBaseUrl) {
+	  presentationBaseURL = presentationBaseUrl;
+	}
+	
+	public void setDocumentConversionService(DocumentConversionService documentConversionService) {
+	  this.documentConversionService = documentConversionService;
+	}
+	
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedDocumentFilter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedDocumentFilter.java
new file mode 100755
index 0000000000..e68e324ac7
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedDocumentFilter.java
@@ -0,0 +1,73 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+import org.bigbluebutton.api.messaging.MessagingConstants;
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.google.gson.Gson;
+
+public class SupportedDocumentFilter {
+	private static Logger log = LoggerFactory.getLogger(SupportedDocumentFilter.class);
+
+	private final MessagingService messagingService;
+	
+	public SupportedDocumentFilter(MessagingService m) {
+		messagingService = m;
+	}
+	
+	public boolean isSupported(UploadedPresentation pres) {
+		File presentationFile = pres.getUploadedFile();
+		
+		/* Get file extension - Perhaps try to rely on a more accurate method than an extension type ? */
+		int fileExtIndex = presentationFile.getName().lastIndexOf('.') + 1;
+		String ext = presentationFile.getName().toLowerCase().substring(fileExtIndex);
+		boolean supported = SupportedFileTypes.isFileSupported(ext);
+		notifyProgressListener(supported, pres);
+		if (supported) {
+			log.info("Received supported file " + pres.getUploadedFile().getAbsolutePath());
+			pres.setFileType(ext);
+		}
+		return supported;
+	}
+	
+	private void notifyProgressListener(boolean supported, UploadedPresentation pres) {
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+						
+		if (supported) {
+			builder.messageKey(ConversionMessageConstants.SUPPORTED_DOCUMENT_KEY);
+		} else {
+			builder.messageKey(ConversionMessageConstants.UNSUPPORTED_DOCUMENT_KEY);
+		}
+		
+		if(messagingService !=null){
+			Gson gson= new Gson();
+			String updateMsg=gson.toJson(builder.build().getMessage());
+			log.debug("sending: "+updateMsg);
+			messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg);
+		} else {
+			log.warn("MessagingService has not been set!");
+		}
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java
new file mode 100755
index 0000000000..ff54f4cfb8
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java
@@ -0,0 +1,80 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import static org.bigbluebutton.presentation.FileTypeConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("serial")
+public final class SupportedFileTypes {
+	// Set as private to prevent instantiation
+	private SupportedFileTypes() {} 
+	
+	private static final List<String> SUPPORTED_FILE_LIST = new ArrayList<String>(15) {		
+		{				
+			// Add all the supported files				
+			add(XLS); add(XLSX);	add(DOC); add(DOCX); add(PPT); add(PPTX);				
+			add(ODT); add(RTF); add(TXT); add(ODS); add(ODP); add(PDF);
+			add(JPG); add(JPEG); add(PNG);
+		}
+	};
+		
+	private static final List<String> OFFICE_FILE_LIST = new ArrayList<String>(11) {		
+		{			
+			// Add all Offile file types
+			add(XLS); add(XLSX);	add(DOC); add(DOCX); add(PPT); add(PPTX);				
+			add(ODT); add(RTF); add(TXT); add(ODS); add(ODP); 
+		}
+	};
+	
+	private static final List<String> IMAGE_FILE_LIST = new ArrayList<String>(3) {		
+		{	
+			// Add all image file types
+			add(JPEG); add(JPG);	add(PNG);
+		}
+	};
+	
+	/*
+	 * Returns if the file with extension is supported.
+	 */
+	public static boolean isFileSupported(String fileExtension) {
+		return SUPPORTED_FILE_LIST.contains(fileExtension.toLowerCase());
+	}
+	
+	/*
+	 * Returns if the office file is supported.
+	 */
+	public static boolean isOfficeFile(String fileExtension) {
+		return OFFICE_FILE_LIST.contains(fileExtension.toLowerCase());
+	}
+	
+	public static boolean isPdfFile(String fileExtension) {
+		return "pdf".equalsIgnoreCase(fileExtension);
+	}
+	
+	/*
+	 * Returns if the iamge file is supported
+	 */
+	public static boolean isImageFile(String fileExtension) {
+		return IMAGE_FILE_LIST.contains(fileExtension.toLowerCase());
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java
new file mode 100755
index 0000000000..d0af7ad08d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java
@@ -0,0 +1,27 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Richard Alam <ritzalam@gmail.com>
+ * 		   DJP <DJP@architectes.org>
+ * 
+ * @version $Id: $
+ */
+package org.bigbluebutton.presentation;
+
+public interface SvgImageCreator {
+	public boolean createSvgImages(UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java
new file mode 100755
index 0000000000..a412755eb3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public interface TextFileCreator {
+	public boolean createTextFiles(UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java
new file mode 100755
index 0000000000..711c5b763d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+public interface ThumbnailCreator {
+	public boolean createThumbnails(UploadedPresentation pres);
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java
new file mode 100755
index 0000000000..ffa5da576c
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java
@@ -0,0 +1,92 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public final class UploadedPresentation {
+	private final String meetingId;
+	private final String id;
+	private final String name;
+	private File uploadedFile;
+	private String fileType = "unknown";
+	private int numberOfPages = 0;
+	private boolean lastStepSuccessful = false;
+	private final String baseUrl;
+	
+	public UploadedPresentation(String meetingId, String id, 
+			                    String name, 
+			                    String baseUrl) {
+		this.meetingId = meetingId;
+		this.id = id;
+		this.name = name;
+		this.baseUrl = baseUrl;
+	}
+
+	public File getUploadedFile() {
+		return uploadedFile;
+	}
+
+	public void setUploadedFile(File uploadedFile) {
+		this.uploadedFile = uploadedFile;
+	}
+
+	public String getMeetingId() {
+		return meetingId;
+	}
+	
+	public String getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+	
+	public String getBaseUrl() {
+		return baseUrl;
+	}
+
+	public String getFileType() {
+		return fileType;
+	}
+
+	public void setFileType(String fileType) {
+		this.fileType = fileType;
+	}
+
+	public int getNumberOfPages() {
+		return numberOfPages;
+	}
+
+	public void setNumberOfPages(int numberOfPages) {
+		this.numberOfPages = numberOfPages;
+	}
+
+	public boolean isLastStepSuccessful() {
+		return lastStepSuccessful;
+	}
+
+	public void setLastStepSuccessful(boolean lastStepSuccessful) {
+		this.lastStepSuccessful = lastStepSuccessful;
+	}
+	
+	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Util.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Util.java
new file mode 100755
index 0000000000..9ac60ce878
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/Util.java
@@ -0,0 +1,44 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation;
+
+import java.io.File;
+
+public final class Util {
+	private Util() {}
+	
+	public static void deleteDirectory(File directory) {
+		/**
+		 * Go through each directory and check if it's not empty.
+		 * We need to delete files inside a directory before a
+		 * directory can be deleted.
+		**/
+		File[] files = directory.listFiles();				
+		for (int i = 0; i < files.length; i++) {
+			if (files[i].isDirectory()) {
+				deleteDirectory(files[i]);
+			} else {
+				files[i].delete();
+			}
+		}
+		// Now that the directory is empty. Delete it.
+		directory.delete();	
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/AbstractPageConverterHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/AbstractPageConverterHandler.java
new file mode 100755
index 0000000000..cb563dde90
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/AbstractPageConverterHandler.java
@@ -0,0 +1,92 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.presentation.handlers;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.nuprocess.NuAbstractProcessHandler;
+import com.zaxxer.nuprocess.NuProcess;
+
+public abstract class AbstractPageConverterHandler extends
+    NuAbstractProcessHandler {
+
+  private static Logger log = LoggerFactory
+      .getLogger(AbstractPageConverterHandler.class);
+
+  protected NuProcess nuProcess;
+  protected int exitCode;
+  final protected StringBuilder stdoutBuilder = new StringBuilder();
+  final protected StringBuilder stderrBuilder = new StringBuilder();
+
+  @Override
+  public void onPreStart(NuProcess nuProcess) {
+    this.nuProcess = nuProcess;
+  }
+
+  @Override
+  public void onStart(NuProcess nuProcess) {
+    super.onStart(nuProcess);
+  }
+
+  @Override
+  public void onStdout(ByteBuffer buffer, boolean closed) {
+    if (buffer != null) {
+      CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
+      stdoutBuilder.append(charBuffer);
+    }
+  }
+
+  @Override
+  public void onStderr(ByteBuffer buffer, boolean closed) {
+    if (buffer != null) {
+      CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
+      stderrBuilder.append(charBuffer);
+    }
+  }
+
+  @Override
+  public void onExit(int statusCode) {
+    exitCode = statusCode;
+  }
+
+  /**
+   * 
+   * @return true if the exit code of the process is different from 0
+   */
+  public Boolean exitedWithError() {
+    return exitCode != 0;
+  }
+
+  protected Boolean stdoutContains(String value) {
+    return stdoutBuilder.indexOf(value) > -1;
+  }
+
+  protected Boolean stderrContains(String value) {
+    return stderrBuilder.indexOf(value) > -1;
+  }
+
+  public Boolean isConversionSuccessfull() {
+    return !exitedWithError();
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2PngPageConverterHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2PngPageConverterHandler.java
new file mode 100755
index 0000000000..493e6f5671
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2PngPageConverterHandler.java
@@ -0,0 +1,30 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.handlers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Pdf2PngPageConverterHandler extends AbstractPageConverterHandler {
+
+  private static Logger log = LoggerFactory
+      .getLogger(Pdf2PngPageConverterHandler.class);
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2SwfPageConverterHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2SwfPageConverterHandler.java
new file mode 100755
index 0000000000..85b016ad48
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Pdf2SwfPageConverterHandler.java
@@ -0,0 +1,109 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.presentation.handlers;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 
+ * The default command output the anlayse looks like the following: </br> 20
+ * DEBUG Using</br> 60 VERBOSE Updating font</br> 80 VERBOSE Drawing
+ *
+ */
+public class Pdf2SwfPageConverterHandler extends AbstractPageConverterHandler {
+
+  private static Logger log = LoggerFactory
+      .getLogger(Pdf2SwfPageConverterHandler.class);
+
+  private static String PLACEMENT_OUTPUT = "DEBUG  Using";
+  private static String TEXT_TAG_OUTPUT = "VERBOSE Updating";
+  private static String IMAGE_TAG_OUTPUT = "VERBOSE Drawing";
+  private static String PLACEMENT_PATTERN = "\\d+\\s" + PLACEMENT_OUTPUT;
+  private static String TEXT_TAG_PATTERN = "\\d+\\s" + TEXT_TAG_OUTPUT;
+  private static String IMAGE_TAG_PATTERN = "\\d+\\s" + IMAGE_TAG_OUTPUT;
+
+  @Override
+  public Boolean isConversionSuccessfull() {
+    return !exitedWithError();
+  }
+
+  /**
+   * 
+   * @return The number of PlaceObject2 tags in the generated SWF
+   */
+  public long numberOfPlacements() {
+    if (stdoutContains(PLACEMENT_OUTPUT)) {
+      try {
+        String out = stdoutBuilder.toString();
+        Pattern r = Pattern.compile(PLACEMENT_PATTERN);
+        Matcher m = r.matcher(out);
+        m.find();
+        return Integer
+            .parseInt(m.group(0).replace(PLACEMENT_OUTPUT, "").trim());
+      } catch (Exception e) {
+        return 0;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * 
+   * @return The number of text tags in the generated SWF.
+   */
+  public long numberOfTextTags() {
+    if (stdoutContains(TEXT_TAG_OUTPUT)) {
+      try {
+        String out = stdoutBuilder.toString();
+        Pattern r = Pattern.compile(TEXT_TAG_PATTERN);
+        Matcher m = r.matcher(out);
+        m.find();
+        return Integer.parseInt(m.group(0).replace(TEXT_TAG_OUTPUT, "").trim());
+      } catch (Exception e) {
+        return 0;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * 
+   * @return The number of image tags in the generated SWF.
+   */
+  public long numberOfImageTags() {
+    if (stdoutContains(IMAGE_TAG_OUTPUT)) {
+      try {
+        String out = stdoutBuilder.toString();
+        Pattern r = Pattern.compile(IMAGE_TAG_PATTERN);
+        Matcher m = r.matcher(out);
+        m.find();
+        return Integer
+            .parseInt(m.group(0).replace(IMAGE_TAG_OUTPUT, "").trim());
+      } catch (Exception e) {
+        return 0;
+      }
+    }
+    return 0;
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Png2SwfPageConverterHandler.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Png2SwfPageConverterHandler.java
new file mode 100755
index 0000000000..1a450b7758
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/handlers/Png2SwfPageConverterHandler.java
@@ -0,0 +1,30 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.handlers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Png2SwfPageConverterHandler extends AbstractPageConverterHandler {
+
+  private static Logger log = LoggerFactory
+      .getLogger(Png2SwfPageConverterHandler.class);
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/CountingPageException.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/CountingPageException.java
new file mode 100755
index 0000000000..9435db9fba
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/CountingPageException.java
@@ -0,0 +1,49 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+@SuppressWarnings("serial")
+public class CountingPageException extends Exception {
+
+	private final int maxNumberOfPages;
+	private final ExceptionType exceptionType;
+	private final int pageCount;
+	
+	public enum ExceptionType {PAGE_COUNT_EXCEPTION, PAGE_EXCEEDED_EXCEPTION};
+	
+	public CountingPageException(ExceptionType type, int pageCount, int maxNumberOfPages) {
+		super("Exception while trying to determine number of pages.");
+		this.pageCount = pageCount;
+		this.maxNumberOfPages = maxNumberOfPages;
+		exceptionType = type;
+	}
+	
+	public int getMaxNumberOfPages() {
+		return maxNumberOfPages;
+	}
+
+	public ExceptionType getExceptionType() {
+		return exceptionType;
+	}
+
+	public int getPageCount() {
+		return pageCount;
+	}	
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java
new file mode 100755
index 0000000000..d28b7dc757
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java
@@ -0,0 +1,77 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A wrapper class the executes an external command. 
+ * 
+ * See http://kylecartmell.com/?p=9
+ * 
+ * @author Richard Alam
+ *
+ */
+public class ExternalProcessExecutor {
+	private static Logger log = LoggerFactory.getLogger(ExternalProcessExecutor.class);
+	
+	public boolean exec(String COMMAND, long timeoutMillis) {
+        Timer timer = null;
+        Process p = null;
+        try {
+            timer = new Timer(false);
+            InterruptTimerTask interrupter = new InterruptTimerTask(Thread.currentThread());
+            timer.schedule(interrupter, timeoutMillis);
+            p = Runtime.getRuntime().exec(COMMAND);
+            p.waitFor();
+            return true;
+        } catch(Exception e) {
+        	log.info("TIMEDOUT excuting : " + COMMAND);
+            p.destroy();
+        } finally {
+            timer.cancel();     // If the process returns within the timeout period, we have to stop the interrupter
+                                // so that it does not unexpectedly interrupt some other code later.
+
+            Thread.interrupted();   // We need to clear the interrupt flag on the current thread just in case
+                                    // interrupter executed after waitFor had already returned but before timer.cancel
+                                    // took effect.
+                                    //
+                                    // Oh, and there's also Sun bug 6420270 to worry about here.
+        }  
+		return false;
+	}
+	
+
+	class InterruptTimerTask extends TimerTask {
+	    private Thread thread;
+
+	    public InterruptTimerTask(Thread t) {
+	        this.thread = t;
+	    }
+
+	    public void run() {
+	        thread.interrupt();
+	    }
+
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/GhostscriptPageExtractor.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/GhostscriptPageExtractor.java
new file mode 100755
index 0000000000..c05a71b8f8
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/GhostscriptPageExtractor.java
@@ -0,0 +1,56 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+import org.bigbluebutton.presentation.PageExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GhostscriptPageExtractor implements PageExtractor {
+	private static Logger log = LoggerFactory.getLogger(GhostscriptPageExtractor.class);
+	
+	private String GHOSTSCRIPT_EXEC;
+	private String noPdfMarkWorkaround;
+	private String SPACE = " ";
+	
+	public boolean extractPage(File presentationFile, File output, int page){		
+		String OPTIONS = "-sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH";
+		String FIRST_PAGE = "-dFirstPage=" + page;
+		String LAST_PAGE = "-dLastPage=" + page;		
+		String DESTINATION = output.getAbsolutePath();
+		String OUTPUT_FILE = "-sOutputFile=" + DESTINATION;
+		
+		//extract that specific page and create a temp-pdf(only one page) with GhostScript
+		String COMMAND = GHOSTSCRIPT_EXEC + SPACE + OPTIONS + SPACE + FIRST_PAGE + SPACE + LAST_PAGE + SPACE 
+							+ OUTPUT_FILE + SPACE + noPdfMarkWorkaround + SPACE + presentationFile.getAbsolutePath();
+		
+        log.debug(COMMAND);
+        return new ExternalProcessExecutor().exec(COMMAND, 60000);
+	}	
+	
+	public void setGhostscriptExec(String exec) {
+		GHOSTSCRIPT_EXEC = exec;
+	}
+	
+	public void setNoPdfMarkWorkaround(String noPdfMarkWorkaround) {
+		this.noPdfMarkWorkaround = noPdfMarkWorkaround;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageMagickPageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageMagickPageConverter.java
new file mode 100755
index 0000000000..05985617a3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageMagickPageConverter.java
@@ -0,0 +1,54 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ImageMagickPageConverter implements PageConverter {
+  private static Logger log = LoggerFactory.getLogger(ImageMagickPageConverter.class);
+
+  private String IMAGEMAGICK_DIR;
+
+  public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){
+
+    String COMMAND = IMAGEMAGICK_DIR + "/convert -depth 8 " + presentationFile.getAbsolutePath() + " " + output.getAbsolutePath();          
+
+    boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);            
+
+    if (done && output.exists()) {
+      return true;		
+    } else {
+      log.warn("Failed to convert: " + output.getAbsolutePath() + " does not exist.");
+      return false;
+    }
+
+  }
+
+  public void setImageMagickDir(String dir) {
+    IMAGEMAGICK_DIR = dir;
+  }
+}
+
+
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java
new file mode 100755
index 0000000000..fe5266b916
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java
@@ -0,0 +1,194 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.ImageToSwfSlide;
+import org.bigbluebutton.presentation.SvgImageCreator;
+import org.bigbluebutton.presentation.TextFileCreator;
+import org.bigbluebutton.presentation.ThumbnailCreator;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ImageToSwfSlidesGenerationService {
+	private static Logger log = LoggerFactory.getLogger(ImageToSwfSlidesGenerationService.class);
+	
+	private ExecutorService executor;
+	private CompletionService<ImageToSwfSlide> completionService;	
+	private SwfSlidesGenerationProgressNotifier notifier;
+	private PageConverter jpgToSwfConverter;
+	private PageConverter pngToSwfConverter;
+	private SvgImageCreator svgImageCreator;
+	private ThumbnailCreator thumbnailCreator;
+	private TextFileCreator textFileCreator;
+	private long MAX_CONVERSION_TIME = 5*60*1000;
+	private String BLANK_SLIDE;
+	
+	public ImageToSwfSlidesGenerationService() {
+		int numThreads = Runtime.getRuntime().availableProcessors();
+		executor = Executors.newFixedThreadPool(numThreads);
+		completionService = new ExecutorCompletionService<ImageToSwfSlide>(executor);
+	}
+	
+	public void generateSlides(UploadedPresentation pres) {
+		pres.setNumberOfPages(1); // There should be only one image to convert.
+		if (pres.getNumberOfPages() > 0) {
+			PageConverter pageConverter = determinePageConverter(pres);
+			convertImageToSwf(pres, pageConverter);
+		}
+		
+		/* adding accessibility */
+		createTextFiles(pres);
+		createThumbnails(pres);
+		createSvgImages(pres);
+		
+		notifier.sendConversionCompletedMessage(pres);
+	}
+	
+	private PageConverter determinePageConverter(UploadedPresentation pres) {
+		String fileType = pres.getFileType().toUpperCase();
+		if (("JPEG".equals(fileType)) || ("JPG".equals(fileType))) {
+			return jpgToSwfConverter;
+		}
+		
+		return pngToSwfConverter;
+	}
+	
+	private void createTextFiles(UploadedPresentation pres) {
+		log.debug("Creating textfiles for accessibility.");
+		notifier.sendCreatingTextFilesUpdateMessage(pres);
+		textFileCreator.createTextFiles(pres);
+	}
+	
+	private void createThumbnails(UploadedPresentation pres) {
+		log.debug("Creating thumbnails.");
+		notifier.sendCreatingThumbnailsUpdateMessage(pres);
+		thumbnailCreator.createThumbnails(pres);
+	}
+	
+	private void createSvgImages(UploadedPresentation pres) {
+		log.debug("Creating SVG images.");
+		notifier.sendCreatingSvgImagesUpdateMessage(pres);
+		svgImageCreator.createSvgImages(pres);
+	}
+	
+	private void convertImageToSwf(UploadedPresentation pres, PageConverter pageConverter) {
+		int numPages = pres.getNumberOfPages();				
+		ImageToSwfSlide[] slides = setupSlides(pres, numPages, pageConverter);
+		generateSlides(slides);		
+		handleSlideGenerationResult(pres, slides);		
+	}
+	
+	private void handleSlideGenerationResult(UploadedPresentation pres, ImageToSwfSlide[] slides) {
+		long endTime = System.currentTimeMillis() + MAX_CONVERSION_TIME;
+		int slideGenerated = 0;
+		
+		for (int t = 0; t < slides.length; t++) {
+			Future<ImageToSwfSlide> future = null;
+			ImageToSwfSlide slide = null;
+			try {
+				long timeLeft = endTime - System.currentTimeMillis();
+				future = completionService.take();
+				slide = future.get(timeLeft, TimeUnit.MILLISECONDS);
+			} catch (InterruptedException e) {
+				log.error("InterruptedException while creating slide " + pres.getName());
+			} catch (ExecutionException e) {
+				log.error("ExecutionException while creating slide " + pres.getName());
+			} catch (TimeoutException e) {
+				log.error("TimeoutException while converting " + pres.getName());				
+			} finally {
+				if ((slide != null) && (! slide.isDone())){
+					log.warn("Creating blank slide for " + slide.getPageNumber());
+					future.cancel(true);
+					slide.generateBlankSlide();
+				}
+			}
+			slideGenerated++;	
+			notifier.sendConversionUpdateMessage(slideGenerated, pres);
+		}
+	}
+	
+	private ImageToSwfSlide[] setupSlides(UploadedPresentation pres, int numPages, PageConverter pageConverter) {
+		ImageToSwfSlide[] slides = new ImageToSwfSlide[numPages];
+		
+		for (int page = 1; page <= numPages; page++) {		
+			ImageToSwfSlide slide = new ImageToSwfSlide(pres, page);
+			slide.setBlankSlide(BLANK_SLIDE);
+			slide.setPageConverter(pageConverter);
+			
+			// Array index is zero-based
+			slides[page-1] = slide;
+		}
+		
+		return slides;
+	}
+	
+	private void generateSlides(ImageToSwfSlide[] slides) {
+		for (int i = 0; i < slides.length; i++) {
+			final ImageToSwfSlide slide = slides[i];
+			completionService.submit(new Callable<ImageToSwfSlide>() {
+				public ImageToSwfSlide call() {
+					return slide.createSlide();
+				}
+			});
+		}
+	}
+		
+	public void setJpgPageConverter(PageConverter converter) {
+		this.jpgToSwfConverter = converter;
+	}
+	
+	public void setPngPageConverter(PageConverter converter) {
+		this.pngToSwfConverter = converter;
+	}
+	
+	public void setBlankSlide(String blankSlide) {
+		this.BLANK_SLIDE = blankSlide;
+	}
+	
+	public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) {
+		this.thumbnailCreator = thumbnailCreator;
+	}
+	public void setTextFileCreator(TextFileCreator textFileCreator) {
+		this.textFileCreator = textFileCreator;
+	}
+	
+	public void setSvgImageCreator(SvgImageCreator svgImageCreator) {
+		this.svgImageCreator = svgImageCreator;
+	}
+	
+	public void setMaxConversionTime(int minutes) {
+		MAX_CONVERSION_TIME = minutes * 60 * 1000;
+	}
+	
+	public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
+		this.notifier = notifier;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java
new file mode 100755
index 0000000000..16e4b28056
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java
@@ -0,0 +1,53 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Jpeg2SwfPageConverter implements PageConverter {
+	private static Logger log = LoggerFactory.getLogger(Jpeg2SwfPageConverter.class);
+	
+	private String SWFTOOLS_DIR;
+	
+	public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){
+		
+        String COMMAND = SWFTOOLS_DIR + "/jpeg2swf -o " + output.getAbsolutePath() + " " + presentationFile.getAbsolutePath();
+        
+        boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);          
+		
+		if (done && output.exists()) {
+			return true;		
+		} else {
+			log.warn("Failed to convert: " + output.getAbsolutePath() + " does not exist.");
+			return false;
+		}
+		
+	}
+	
+	public void setSwfToolsDir(String dir) {
+		SWFTOOLS_DIR = dir;
+	}
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Office2PdfPageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Office2PdfPageConverter.java
new file mode 100755
index 0000000000..9a0eeadb8d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Office2PdfPageConverter.java
@@ -0,0 +1,71 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.artofsolving.jodconverter.*;
+import com.artofsolving.jodconverter.openoffice.connection.*;
+import com.artofsolving.jodconverter.openoffice.converter.*;
+
+public class Office2PdfPageConverter implements PageConverter {
+  private static Logger log = LoggerFactory.getLogger(Office2PdfPageConverter.class);
+
+  public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){
+    SocketOpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
+
+    try {
+      connection.connect();
+
+      log.debug("Converting " + presentationFile.getAbsolutePath() + " to " + output.getAbsolutePath());
+
+      DefaultDocumentFormatRegistry registry = new DefaultDocumentFormatRegistry();
+      OpenOfficeDocumentConverter converter = new OpenOfficeDocumentConverter(connection, registry);
+
+      DocumentFormat pdf = registry.getFormatByFileExtension("pdf");
+      Map<String, Object> pdfOptions = new HashMap<String, Object>();
+      pdfOptions.put("ReduceImageResolution", Boolean.TRUE);
+      pdfOptions.put("MaxImageResolution", Integer.valueOf(300));
+      pdf.setExportOption(DocumentFamily.TEXT, "FilterData", pdfOptions);
+
+      converter.convert(presentationFile, output, pdf);
+      connection.disconnect();
+
+      if (output.exists()) {
+        return true;
+      } else {
+        log.warn("Failed to convert: " + output.getAbsolutePath() + " does not exist.");
+        return false;
+      }
+
+    } catch(Exception e) {
+      log.error("Exception: Failed to convert " + output.getAbsolutePath());
+      return false;
+    }
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java
new file mode 100755
index 0000000000..9dd17f4f17
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java
@@ -0,0 +1,70 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.SupportedFileTypes;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OfficeToPdfConversionService {
+  private static Logger log = LoggerFactory.getLogger(OfficeToPdfConversionService.class);	
+
+  /*
+   * Convert the Office document to PDF. If successful, update 
+   * UploadPresentation.uploadedFile with the new PDF out and
+   * UploadPresentation.lastStepSuccessful to TRUE.
+   */
+  public UploadedPresentation convertOfficeToPdf(UploadedPresentation pres) {
+    initialize(pres);
+    if (SupportedFileTypes.isOfficeFile(pres.getFileType())) {
+      File pdfOutput = setupOutputPdfFile(pres);				
+      if (convertOfficeDocToPdf(pres, pdfOutput)) {
+        log.info("Successfully converted office file to pdf.");
+        makePdfTheUploadedFileAndSetStepAsSuccess(pres, pdfOutput);
+      } else {
+        log.warn("Failed to convert " + pres.getUploadedFile().getAbsolutePath() + " to Pdf.");
+      }
+    }
+    return pres;
+  }
+
+  public void initialize(UploadedPresentation pres) {
+    pres.setLastStepSuccessful(false);
+  }
+
+  private File setupOutputPdfFile(UploadedPresentation pres) {		
+    File presentationFile = pres.getUploadedFile();
+    String filenameWithoutExt = presentationFile.getAbsolutePath().substring(0, presentationFile.getAbsolutePath().lastIndexOf("."));
+    return new File(filenameWithoutExt + ".pdf");
+  }
+
+  private boolean convertOfficeDocToPdf(UploadedPresentation pres, File pdfOutput) {
+    PageConverter converter = new Office2PdfPageConverter();
+    return converter.convert(pres.getUploadedFile(), pdfOutput, 0, pres);
+  }
+
+  private void makePdfTheUploadedFileAndSetStepAsSuccess(UploadedPresentation pres, File pdf) {
+    pres.setUploadedFile(pdf);
+    pres.setLastStepSuccessful(true);
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PageCounterService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PageCounterService.java
new file mode 100755
index 0000000000..9d91b9985a
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PageCounterService.java
@@ -0,0 +1,85 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import org.bigbluebutton.presentation.PageCounter;
+import org.bigbluebutton.presentation.SupportedFileTypes;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PageCounterService {	
+	private static Logger log = LoggerFactory.getLogger(PageCounterService.class);
+	
+	private int maxNumPages = 100;
+	private PageCounter pageCounter;
+	
+	public void determineNumberOfPages(UploadedPresentation pres) throws CountingPageException {
+		int numberOfPages = 0;
+		if (SupportedFileTypes.isPdfFile(pres.getFileType())) {
+			numberOfPages = countPages(pres);			
+		} else if (SupportedFileTypes.isImageFile(pres.getFileType())) {
+			numberOfPages = 1;
+		}
+		
+		if (isNumberOfPagesValid(numberOfPages)) {
+			pres.setNumberOfPages(numberOfPages);
+		}		
+	}
+
+	private boolean isNumberOfPagesValid(int numberOfPages) throws CountingPageException {
+		if (numberOfPages <= 0) {
+			throw new CountingPageException(CountingPageException.ExceptionType.PAGE_COUNT_EXCEPTION, 0, maxNumPages);
+		} 
+		
+		if (checkIfNumberOfPagesExceedsLimit(numberOfPages)) {
+			throw new CountingPageException(CountingPageException.ExceptionType.PAGE_EXCEEDED_EXCEPTION, numberOfPages, maxNumPages);
+		}
+		
+		return true;
+	}
+	
+	private boolean checkIfNumberOfPagesExceedsLimit(int numberOfPages) {
+		if (numberOfPages > maxNumPages) {
+			return true;
+		}
+		return false;
+	}
+	
+	private int countPages(UploadedPresentation pres) {
+		int numPages = 0;
+		
+		if (pageCounter == null) {
+			log.warn("No page counter!");
+			return 0;
+		}
+		
+		numPages = pageCounter.countNumberOfPages(pres.getUploadedFile());
+		return numPages;
+	}
+			
+	public void setMaxNumPages(int maxPages) {
+		maxNumPages = maxPages;
+	}
+		
+	public void setPageCounter(PageCounter pageCounter) {
+		this.pageCounter = pageCounter;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java
new file mode 100755
index 0000000000..42ae5dcfbf
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java
@@ -0,0 +1,263 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.FilenameUtils;
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.bigbluebutton.presentation.handlers.Pdf2PngPageConverterHandler;
+import org.bigbluebutton.presentation.handlers.Pdf2SwfPageConverterHandler;
+import org.bigbluebutton.presentation.handlers.Png2SwfPageConverterHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.zaxxer.nuprocess.NuAbstractProcessHandler;
+import com.zaxxer.nuprocess.NuProcess;
+import com.zaxxer.nuprocess.NuProcessBuilder;
+
+public class Pdf2SwfPageConverter implements PageConverter {
+  private static Logger log = LoggerFactory
+      .getLogger(Pdf2SwfPageConverter.class);
+
+  private String GHOSTSCRIPT_EXEC;
+  private String IMAGEMAGICK_DIR;
+  private String SWFTOOLS_DIR;
+  private String fontsDir;
+  private String noPdfMarkWorkaround;
+  private long placementsThreshold;
+  private long defineTextThreshold;
+  private long imageTagThreshold;
+  private String convTimeout = "5s";
+  private int WAIT_FOR_SEC = 6;
+
+  public boolean convert(File presentation, File output, int page, UploadedPresentation pres) {
+    long convertStart = System.currentTimeMillis();
+
+    String source = presentation.getAbsolutePath();
+    String dest = output.getAbsolutePath();
+    String AVM2SWF = "-T9";
+
+    // Building the command line wrapped in shell to be able to use shell
+    // feature like the pipe
+    NuProcessBuilder pb = new NuProcessBuilder(
+        Arrays.asList("timeout", convTimeout,
+            "/bin/sh",
+            "-c",
+            SWFTOOLS_DIR
+            + File.separator
+            + "pdf2swf"
+            + " -vv "
+            + AVM2SWF
+            + " -F "
+            + fontsDir
+            + " -p "
+            + String.valueOf(page)
+            + " "
+            + source
+            + " -o "
+            + dest
+            + " | egrep  'shape id|Updating font|Drawing' | sed 's/  / /g' | cut -d' ' -f 1-3  | sort | uniq -cw 15"));
+
+    Pdf2SwfPageConverterHandler pHandler = new Pdf2SwfPageConverterHandler();
+    pb.setProcessListener(pHandler);
+
+    long pdf2SwfStart = System.currentTimeMillis();
+
+    NuProcess process = pb.start();
+    try {
+      process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      log.error(e.getMessage());
+    }
+
+    long pdf2SwfEnd = System.currentTimeMillis();   
+    log.debug("Pdf2Swf conversion duration: {} sec", (pdf2SwfEnd - pdf2SwfStart)/1000);
+
+    File destFile = new File(dest);
+    if (pHandler.isConversionSuccessfull() && destFile.exists()
+        && pHandler.numberOfPlacements() < placementsThreshold
+        && pHandler.numberOfTextTags() < defineTextThreshold
+        && pHandler.numberOfImageTags() < imageTagThreshold) {
+      return true;
+    } else {
+      Map<String, Object> logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+      logData.put("convertSuccess", pHandler.isConversionSuccessfull());
+      logData.put("fileExists", destFile.exists());
+      logData.put("numObjectTags", pHandler.numberOfPlacements());
+      logData.put("numTextTags", pHandler.numberOfTextTags());
+      logData.put("numImageTags", pHandler.numberOfImageTags());
+      Gson gson = new Gson();
+      String logStr =  gson.toJson(logData);
+
+      log.warn("Potential problem with generated SWF: data={}", logStr);
+
+      File tempPdfPage = null;
+      File tempPng = null;
+      String basePresentationame = FilenameUtils.getBaseName(presentation.getName());
+      try {
+        tempPdfPage = File.createTempFile(basePresentationame + "-" + page, ".pdf");
+        tempPng = File.createTempFile(basePresentationame + "-" + page, ".png");
+      } catch (IOException ioException) {
+        // We should never fall into this if the server is correctly configured
+        log.error("Unable to create temporary files");
+      }
+
+      long gsStart = System.currentTimeMillis();
+
+      // Step 1: Extract the PDF page into a single PDF file
+      NuProcessBuilder pbPdf = new NuProcessBuilder(Arrays.asList("timeout", convTimeout,
+          GHOSTSCRIPT_EXEC, "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dQUIET",
+          "-dBATCH", "-dFirstPage=" + page, "-dLastPage=" + page,
+          "-sOutputFile=" + tempPdfPage.getAbsolutePath(), noPdfMarkWorkaround,
+          presentation.getAbsolutePath()));
+
+      NuAbstractProcessHandler pbPdfHandler = new NuAbstractProcessHandler() {};
+      pbPdf.setProcessListener(pbPdfHandler);
+      NuProcess processPdf = pbPdf.start();
+      try {
+        processPdf.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        log.error(e.getMessage());
+      }
+
+      long gsEnd = System.currentTimeMillis();
+      log.debug("Ghostscript conversion duration: {} sec", (gsStart - gsEnd)/1000);
+
+      long magickStart = System.currentTimeMillis();
+
+      // Step 2: Convert a PDF page to PNG
+      NuProcessBuilder pbPng = new NuProcessBuilder(Arrays.asList("timeout", convTimeout,
+          IMAGEMAGICK_DIR + File.separator + "convert", "-density", "150",
+          "-quality", "90", "-flatten", "+dither", "-depth", "8",
+          tempPdfPage.getAbsolutePath(), tempPng.getAbsolutePath()));
+      Pdf2PngPageConverterHandler pbPngHandler = new Pdf2PngPageConverterHandler();
+      pbPng.setProcessListener(pbPngHandler);
+      NuProcess processPng = pbPng.start();
+      try {
+        processPng.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        log.error(e.getMessage());
+      }
+      long magickEnd = System.currentTimeMillis();
+
+      logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+      logData.put("conversionTime(sec)", (magickEnd - magickStart)/1000);
+
+      logStr =  gson.toJson(logData);
+
+      log.debug("ImageMagick conversion duration: {} sec", (magickEnd - magickStart)/1000);
+
+      long png2swfStart = System.currentTimeMillis();
+
+      // Step 3: Convert a PNG image to SWF
+      source = tempPng.getAbsolutePath();
+      NuProcessBuilder pbSwf = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, SWFTOOLS_DIR
+          + File.separator + "png2swf", "-o", dest, source));
+      Png2SwfPageConverterHandler pSwfHandler = new Png2SwfPageConverterHandler();
+      pbSwf.setProcessListener(pSwfHandler);
+      NuProcess processSwf = pbSwf.start();
+      try {
+        processSwf.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        log.error(e.getMessage());
+      }
+
+      long png2swfEnd = System.currentTimeMillis();
+      log.debug("ImageMagick conversion duration: {} sec", (png2swfEnd - png2swfStart)/1000);
+
+      // Delete the temporary PNG and PDF files after finishing the image
+      // conversion
+      tempPdfPage.delete();
+      tempPng.delete();
+
+      boolean doneSwf = pSwfHandler.isConversionSuccessfull();
+
+      long convertEnd = System.currentTimeMillis();
+
+      logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", page);
+      logData.put("conversionTime(sec)", (convertEnd - convertStart)/1000);
+
+      logStr =  gson.toJson(logData);
+
+      log.debug("Problem page conversion duration: {} sec", (convertEnd - convertStart)/1000);
+
+
+      if (doneSwf && destFile.exists()) {
+        return true;
+      } else {
+        log.warn("Failed to convert: " + destFile + " does not exist.");
+        return false;
+      }
+    }
+  }
+
+  public void setSwfToolsDir(String dir) {
+    SWFTOOLS_DIR = dir;
+  }
+
+  public void setImageMagickDir(String dir) {
+    IMAGEMAGICK_DIR = dir;
+  }
+
+  public void setFontsDir(String dir) {
+    fontsDir = dir;
+  }
+
+  public void setPlacementsThreshold(long threshold) {
+    placementsThreshold = threshold;
+  }
+
+  public void setDefineTextThreshold(long threshold) {
+    defineTextThreshold = threshold;
+  }
+
+  public void setImageTagThreshold(long threshold) {
+    imageTagThreshold = threshold;
+  }
+
+  public void setGhostscriptExec(String exec) {
+    GHOSTSCRIPT_EXEC = exec;
+  }
+
+  public void setNoPdfMarkWorkaround(String noPdfMarkWorkaround) {
+    this.noPdfMarkWorkaround = noPdfMarkWorkaround;
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageCounter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageCounter.java
new file mode 100755
index 0000000000..74fc9385c2
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageCounter.java
@@ -0,0 +1,106 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.bigbluebutton.presentation.PageCounter;
+import org.bigbluebutton.presentation.imp.ExternalProcessExecutor.InterruptTimerTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Pdf2SwfPageCounter implements PageCounter {
+	private static Logger log = LoggerFactory.getLogger(Pdf2SwfPageCounter.class);
+	
+	private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("page=([0-9]+)(?: .+)");	
+	private String SWFTOOLS_DIR;
+
+	public int countNumberOfPages(File presentationFile) {		
+		int numPages = 0; //total numbers of this pdf	
+
+		String COMMAND = SWFTOOLS_DIR + "/pdf2swf -I " + presentationFile.getAbsolutePath(); 
+   	
+        Timer timer = null;
+        Process p = null;
+        try {
+            timer = new Timer(true);
+            InterruptTimerTask interrupter = new InterruptTimerTask(Thread.currentThread());
+            timer.schedule(interrupter, 60000);
+            
+            p = Runtime.getRuntime().exec(COMMAND); 
+            BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
+			BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+			String info;
+			Matcher matcher;
+			while ((info = stdInput.readLine()) != null) {
+				//The output would be something like this 'page=21 width=718.00 height=538.00'.
+	    		//We need to extract the page number (i.e. 21) from it.
+				matcher = PAGE_NUMBER_PATTERN.matcher(info);
+	    		if (matcher.matches()) {
+	    			numPages = Integer.valueOf(matcher.group(1).trim()).intValue();
+	    		}
+			}
+			while ((info = stdError.readLine()) != null) {
+				log.error(info);
+			}
+			stdInput.close();
+			stdError.close();
+            p.waitFor();
+        } catch(Exception e) {
+        	log.info("TIMEDOUT excuting : " + COMMAND);
+            p.destroy();
+        } finally {
+            timer.cancel();     // If the process returns within the timeout period, we have to stop the interrupter
+                                // so that it does not unexpectedly interrupt some other code later.
+
+            Thread.interrupted();   // We need to clear the interrupt flag on the current thread just in case
+                                    // interrupter executed after waitFor had already returned but before timer.cancel
+                                    // took effect.
+                                    //
+                                    // Oh, and there's also Sun bug 6420270 to worry about here.
+        }  
+
+		return numPages;
+	}
+		
+	public void setSwfToolsDir(String dir) {
+		SWFTOOLS_DIR = dir;
+	}
+	
+	class InterruptTimerTask extends TimerTask {
+	    private Thread thread;
+
+	    public InterruptTimerTask(Thread t) {
+	        this.thread = t;
+	    }
+
+	    public void run() {
+	        thread.interrupt();
+	    }
+
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfPageToImageConversionService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfPageToImageConversionService.java
new file mode 100755
index 0000000000..6d6bac6985
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfPageToImageConversionService.java
@@ -0,0 +1,67 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.PageExtractor;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PdfPageToImageConversionService {
+  private static Logger log = LoggerFactory.getLogger(PdfPageToImageConversionService.class);
+
+  private PageExtractor extractor;
+  private PageConverter pdfToImageConverter;
+  private PageConverter imageToSwfConverter;
+
+  public boolean convertPageAsAnImage(File presentationFile, File output, int page, UploadedPresentation pres) {
+    File tempDir = new File(presentationFile.getParent() + File.separatorChar + "temp");
+    tempDir.mkdir();
+
+    File tempPdfFile = new File(tempDir.getAbsolutePath() + File.separator + "temp-" + page + ".pdf");
+
+    if (extractor.extractPage(presentationFile, tempPdfFile, page)) {
+      File tempPngFile = new File(tempDir.getAbsolutePath() + "/temp-" + page + ".svg");
+
+      if (pdfToImageConverter.convert(tempPdfFile, tempPngFile, 1, pres)) {
+        if (imageToSwfConverter.convert(tempPngFile, output, 1, pres)) {
+          return true;
+        }
+      }
+    }		
+
+    return false;
+  }
+
+  public void setPageExtractor(PageExtractor extractor) {
+    this.extractor = extractor;
+  }
+
+  public void setPdfToImageConverter(PageConverter imageConverter) {
+    this.pdfToImageConverter = imageConverter;
+  }
+
+  public void setImageToSwfConverter(PageConverter swfConverter) {
+    this.imageToSwfConverter = swfConverter;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java
new file mode 100755
index 0000000000..a4e3369bd0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java
@@ -0,0 +1,302 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.bigbluebutton.presentation.ConversionMessageConstants;
+import org.bigbluebutton.presentation.ConversionUpdateMessage;
+import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.PdfToSwfSlide;
+import org.bigbluebutton.presentation.SvgImageCreator;
+import org.bigbluebutton.presentation.TextFileCreator;
+import org.bigbluebutton.presentation.ThumbnailCreator;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+public class PdfToSwfSlidesGenerationService {
+  private static Logger log = LoggerFactory.getLogger(PdfToSwfSlidesGenerationService.class);
+
+  private SwfSlidesGenerationProgressNotifier notifier;
+  private PageCounterService counterService;
+  private PageConverter pdfToSwfConverter;
+  private PdfPageToImageConversionService imageConvertService;
+  private ExecutorService executor;  
+  private ThumbnailCreator thumbnailCreator;
+  private TextFileCreator textFileCreator;
+  private SvgImageCreator svgImageCreator;
+  private long MAX_CONVERSION_TIME = 5*60*1000;
+  private String BLANK_SLIDE;
+  private int MAX_SWF_FILE_SIZE;
+  private boolean svgImagesRequired;
+  private final long CONVERSION_TIMEOUT = 20000000000L; // 20s
+  private int NUM_CONVERSION_THREADS = 2;
+
+  public PdfToSwfSlidesGenerationService(int numConversionThreads) {
+    executor = Executors.newFixedThreadPool(numConversionThreads);
+  }
+
+  public void generateSlides(UploadedPresentation pres) {
+    determineNumberOfPages(pres);
+    if (pres.getNumberOfPages() > 0) {
+      convertPdfToSwf(pres);
+      createTextFiles(pres);
+      createThumbnails(pres);
+
+      // only create SVG images if the configuration requires it
+      if (svgImagesRequired) {
+        createSvgImages(pres);
+      }
+
+      notifier.sendConversionCompletedMessage(pres);
+    }		
+  }
+
+  private boolean determineNumberOfPages(UploadedPresentation pres) {
+    try {
+      counterService.determineNumberOfPages(pres);
+      return true;
+    } catch (CountingPageException e) {
+      sendFailedToCountPageMessage(e, pres);
+    }
+    return false;
+  }
+
+  private void sendFailedToCountPageMessage(CountingPageException e, UploadedPresentation pres) {
+    MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+
+    if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_COUNT_EXCEPTION) {
+      builder.messageKey(ConversionMessageConstants.PAGE_COUNT_FAILED_KEY);			
+    } else if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_EXCEEDED_EXCEPTION) {
+      builder.numberOfPages(e.getPageCount());
+      builder.maxNumberPages(e.getMaxNumberOfPages());
+      builder.messageKey(ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY);
+    }
+    notifier.sendConversionUpdateMessage(builder.build().getMessage());
+  }
+
+  private void createThumbnails(UploadedPresentation pres) {
+    notifier.sendCreatingThumbnailsUpdateMessage(pres);
+    thumbnailCreator.createThumbnails(pres);
+  }
+
+  private void createTextFiles(UploadedPresentation pres) {
+    notifier.sendCreatingTextFilesUpdateMessage(pres);
+    textFileCreator.createTextFiles(pres);
+  }
+
+  private void createSvgImages(UploadedPresentation pres) {
+    notifier.sendCreatingSvgImagesUpdateMessage(pres);
+    svgImageCreator.createSvgImages(pres);
+  }
+
+  private void convertPdfToSwf(UploadedPresentation pres) {
+    int numPages = pres.getNumberOfPages();				
+    List<PdfToSwfSlide> slides = setupSlides(pres, numPages);
+
+    CompletionService<PdfToSwfSlide> completionService = new ExecutorCompletionService<PdfToSwfSlide>(executor);
+
+    generateSlides(pres, slides, completionService);		
+  }
+
+  private void generateSlides(UploadedPresentation pres, List<PdfToSwfSlide> slides, CompletionService<PdfToSwfSlide> completionService) {
+    long MAXWAIT = MAX_CONVERSION_TIME * 60 /*seconds*/ * 1000 /*millis*/;
+    int slidesCompleted = 0;
+
+    long presConvStart = System.currentTimeMillis();
+
+    for (final PdfToSwfSlide slide : slides) {
+      long pageConvStart = System.currentTimeMillis();
+
+      Callable<PdfToSwfSlide> c = new Callable<PdfToSwfSlide>() {
+        public PdfToSwfSlide call() {
+          return slide.createSlide();
+        };
+      };
+
+      Future<PdfToSwfSlide> f = executor.submit(c);
+      long endNanos = System.nanoTime() + CONVERSION_TIMEOUT;
+      try {
+        // Only wait for the remaining time budget         
+        long timeLeft = endNanos - System.nanoTime();  
+        PdfToSwfSlide s = f.get(timeLeft, TimeUnit.NANOSECONDS);
+        slidesCompleted++;
+        notifier.sendConversionUpdateMessage(slidesCompleted, pres);
+      } catch (ExecutionException e) {     
+        Map<String, Object> logData = new HashMap<String, Object>();
+        logData.put("meetingId", pres.getMeetingId());
+        logData.put("presId", pres.getId());
+        logData.put("filename", pres.getName());
+        logData.put("page", slide.getPageNumber());
+
+        Gson gson = new Gson();
+        String logStr =  gson.toJson(logData);
+
+        log.warn("ExecutionException while converting page: data={}", logStr);
+        log.error(e.getMessage());
+      } catch (InterruptedException e) {        
+        Map<String, Object> logData = new HashMap<String, Object>();
+        logData.put("meetingId", pres.getMeetingId());
+        logData.put("presId", pres.getId());
+        logData.put("filename", pres.getName());
+        logData.put("page", slide.getPageNumber());
+
+        Gson gson = new Gson();
+        String logStr =  gson.toJson(logData);
+
+        log.warn("InterruptedException while converting page: data={}", logStr);
+        Thread.currentThread().interrupt();         
+      } catch (TimeoutException e) {               
+        Map<String, Object> logData = new HashMap<String, Object>();
+        logData.put("meetingId", pres.getMeetingId());
+        logData.put("presId", pres.getId());
+        logData.put("filename", pres.getName());
+        logData.put("page", slide.getPageNumber());
+
+        Gson gson = new Gson();
+        String logStr =  gson.toJson(logData);
+
+        log.warn("TimeoutException while converting page: data={}", logStr);
+        f.cancel(true);     
+      } 
+
+      long pageConvEnd = System.currentTimeMillis();
+      Map<String, Object> logData = new HashMap<String, Object>();
+      logData.put("meetingId", pres.getMeetingId());
+      logData.put("presId", pres.getId());
+      logData.put("filename", pres.getName());
+      logData.put("page", slide.getPageNumber());
+      logData.put("conversionTime(sec)", (pageConvEnd - pageConvStart)/1000);
+      Gson gson = new Gson();
+      String logStr =  gson.toJson(logData);
+
+      log.debug("Page conversion duration(sec): data={}", logStr);
+
+    }   
+
+    for (final PdfToSwfSlide slide : slides) {
+      if (! slide.isDone()){
+
+        slide.generateBlankSlide();      
+
+        Map<String, Object> logData = new HashMap<String, Object>();
+        logData.put("meetingId", pres.getMeetingId());
+        logData.put("presId", pres.getId());
+        logData.put("filename", pres.getName());
+
+        Gson gson = new Gson();
+        String logStr =  gson.toJson(logData);
+
+        log.warn("Creating blank slide: data={}", logStr);
+
+        notifier.sendConversionUpdateMessage(slidesCompleted++, pres);
+      } 
+    }
+
+    long presConvEnd = System.currentTimeMillis();
+    Map<String, Object> logData = new HashMap<String, Object>();
+    logData.put("meetingId", pres.getMeetingId());
+    logData.put("presId", pres.getId());
+    logData.put("filename", pres.getName());
+    logData.put("conversionTime(sec)", (presConvEnd - presConvStart)/1000);
+    Gson gson = new Gson();
+    String logStr =  gson.toJson(logData);
+
+    log.debug("Presentation conversion duration (sec): data={}", logStr);
+  }
+
+
+  private List<PdfToSwfSlide> setupSlides(UploadedPresentation pres, int numPages) {
+    List<PdfToSwfSlide> slides = new ArrayList<PdfToSwfSlide>(numPages);
+
+    for (int page = 1; page <= numPages; page++) {		
+      PdfToSwfSlide slide = new PdfToSwfSlide(pres, page);
+      slide.setBlankSlide(BLANK_SLIDE);
+      slide.setMaxSwfFileSize(MAX_SWF_FILE_SIZE);
+      slide.setPageConverter(pdfToSwfConverter);
+      slide.setPdfPageToImageConversionService(imageConvertService);
+
+      slides.add(slide);
+    }
+
+    return slides;
+  }
+
+
+
+  public void setCounterService(PageCounterService counterService) {
+    this.counterService = counterService;
+  }
+
+  public void setPageConverter(PageConverter converter) {
+    this.pdfToSwfConverter = converter;
+  }
+
+  public void setPdfPageToImageConversionService(PdfPageToImageConversionService service) {
+    this.imageConvertService = service;
+  }
+
+  public void setBlankSlide(String blankSlide) {
+    this.BLANK_SLIDE = blankSlide;
+  }
+
+  public void setMaxSwfFileSize(int size) {
+    this.MAX_SWF_FILE_SIZE = size;
+  }
+
+  public void setSvgImagesRequired(boolean svg) {
+    this.svgImagesRequired = svg;
+  }
+
+  public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) {
+    this.thumbnailCreator = thumbnailCreator;
+  }
+  public void setTextFileCreator(TextFileCreator textFileCreator) {
+    this.textFileCreator = textFileCreator;
+  }
+  public void setSvgImageCreator(SvgImageCreator svgImageCreator) {
+    this.svgImageCreator = svgImageCreator;
+  }
+  public void setMaxConversionTime(int minutes) {
+    MAX_CONVERSION_TIME = minutes * 60 * 1000;
+  }
+
+  public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
+    this.notifier = notifier;
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java
new file mode 100755
index 0000000000..e7c72f6703
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java
@@ -0,0 +1,51 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program 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.0 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+
+import org.bigbluebutton.presentation.PageConverter;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Png2SwfPageConverter implements PageConverter {
+  private static Logger log = LoggerFactory.getLogger(Png2SwfPageConverter.class);
+
+  private String SWFTOOLS_DIR;
+
+  public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){		
+    String COMMAND = SWFTOOLS_DIR + "/png2swf -o " + output.getAbsolutePath() + " " + presentationFile.getAbsolutePath();		
+
+    boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); 	            
+
+    if (done && output.exists()) {
+      return true;		
+    } else {
+      log.warn("Failed to convert: " + output.getAbsolutePath() + " does not exist.");
+      return false;
+    }
+  }
+
+  public void setSwfToolsDir(String dir) {
+    SWFTOOLS_DIR = dir;
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
new file mode 100755
index 0000000000..ce76de22c7
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java
@@ -0,0 +1,105 @@
+package org.bigbluebutton.presentation.imp;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.bigbluebutton.presentation.SvgImageCreator;
+import org.bigbluebutton.presentation.SupportedFileTypes;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SvgImageCreatorImp implements SvgImageCreator {
+	private static Logger log = LoggerFactory.getLogger(SvgImageCreatorImp.class);
+	
+	private String IMAGEMAGICK_DIR;
+	
+	@Override
+	public boolean createSvgImages(UploadedPresentation pres) {
+		boolean success = false;
+		File imagePresentationDir = determineSvgImagesDirectory(pres.getUploadedFile());
+		if (! imagePresentationDir.exists())
+			imagePresentationDir.mkdir();
+		
+		cleanDirectory(imagePresentationDir);
+		
+		try {
+			extractPdfPages(pres);
+			success = generateSvgImages(imagePresentationDir,pres);
+	    } catch (InterruptedException e) {
+	    	log.warn("Interrupted Exception while generating images.");
+	        success = false;
+	    }
+		
+		return success;
+	}
+	
+	private void extractPdfPages(UploadedPresentation pres){
+		File tmpDir = new File(pres.getUploadedFile().getParent() + File.separatorChar + "svgs" + File.separatorChar + "tmp");
+		if (! tmpDir.exists())
+			tmpDir.mkdir();
+		
+	 	if(SupportedFileTypes.isPdfFile(pres.getFileType())){
+	 		for(int i=1; i<=pres.getNumberOfPages(); i++){
+	 			File tmp = new File(tmpDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".pdf");
+	 			String COMMAND = IMAGEMAGICK_DIR + "/gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dFirstPage=" + i + " -dLastPage=" + i + " -sOutputFile=" + tmp.getAbsolutePath() + " /etc/bigbluebutton/nopdfmark.ps " + pres.getUploadedFile().getAbsolutePath();
+	 			new ExternalProcessExecutor().exec(COMMAND, 60000);
+	 		}
+	 		
+	 	}
+	}
+
+	private boolean generateSvgImages(File imagePresentationDir, UploadedPresentation pres) throws InterruptedException {
+		String source = pres.getUploadedFile().getAbsolutePath();
+	 	String dest;
+	 	String COMMAND = "";
+	 	boolean done = true;
+	 	if(SupportedFileTypes.isImageFile(pres.getFileType())){
+                       dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
+	 		COMMAND = IMAGEMAGICK_DIR + "/convert " + source + " " + dest;
+	 		done = new ExternalProcessExecutor().exec(COMMAND, 60000);
+
+                       source = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
+                       dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.svg";
+                       COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + source + " " + dest;
+                       done = new ExternalProcessExecutor().exec(COMMAND, 60000);
+		 	
+	 	}else{
+	 		for(int i=1; i<=pres.getNumberOfPages(); i++){
+	 			File tmp = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "tmp" + File.separatorChar + "slide" + i + ".pdf");
+	 			File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".svg");
+				COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + File.separatorChar + tmp.getAbsolutePath() + " " + destsvg.getAbsolutePath();
+
+	 			done = new ExternalProcessExecutor().exec(COMMAND, 60000);
+	 			if(!done){
+	 				break;
+	 			}
+	 		}
+	 	}
+	 	
+	 	if (done) {
+	 		return true;
+	 	} 
+	 	log.warn("Failed to create svg images: " + COMMAND);	 		
+		return false;
+	}
+	
+	private File determineSvgImagesDirectory(File presentationFile) {
+		return new File(presentationFile.getParent() + File.separatorChar + "svgs");
+	}
+	
+	private void cleanDirectory(File directory) {	
+		File[] files = directory.listFiles();				
+		for (int i = 0; i < files.length; i++) {
+			files[i].delete();
+		}
+	}
+
+	public void setImageMagickDir(String imageMagickDir) {
+		IMAGEMAGICK_DIR = imageMagickDir;
+	}
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java
new file mode 100755
index 0000000000..1fda818a94
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java
@@ -0,0 +1,104 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.util.Map;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.bigbluebutton.api.messaging.MessagingConstants;
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.bigbluebutton.presentation.ConversionMessageConstants;
+import org.bigbluebutton.presentation.ConversionUpdateMessage;
+import org.bigbluebutton.presentation.GeneratedSlidesInfoHelper;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+public class SwfSlidesGenerationProgressNotifier {
+	private static Logger log = LoggerFactory.getLogger(SwfSlidesGenerationProgressNotifier.class);
+
+	private MessagingService messagingService;
+	
+	private GeneratedSlidesInfoHelper generatedSlidesInfoHelper;
+			
+	private void notifyProgressListener(Map<String, Object> msg) {	
+		if(messagingService != null){
+			Gson gson= new Gson();
+			String updateMsg = gson.toJson(msg);
+			messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg);
+		} else {
+			log.warn("MessagingService has not been set");
+		}
+	}
+
+	public void sendConversionUpdateMessage(Map<String, Object> message) {
+		notifyProgressListener(message);
+	}
+	
+	public void sendConversionUpdateMessage(int slidesCompleted, UploadedPresentation pres) {
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+		builder.messageKey(ConversionMessageConstants.GENERATED_SLIDE_KEY);
+		builder.numberOfPages(pres.getNumberOfPages());
+		builder.pagesCompleted(slidesCompleted);
+		notifyProgressListener(builder.build().getMessage());
+	}
+	
+	public void sendCreatingThumbnailsUpdateMessage(UploadedPresentation pres) {
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+		builder.messageKey(ConversionMessageConstants.GENERATING_THUMBNAIL_KEY);
+		notifyProgressListener(builder.build().getMessage());		
+	}
+	
+	public void sendConversionCompletedMessage(UploadedPresentation pres) {	
+		if (generatedSlidesInfoHelper == null) {
+			log.error("GeneratedSlidesInfoHelper was not set. Could not notify interested listeners.");
+			return;
+		}
+		
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+		builder.messageKey(ConversionMessageConstants.CONVERSION_COMPLETED_KEY);		
+		builder.numberOfPages(pres.getNumberOfPages());
+		builder.presBaseUrl(pres);
+		notifyProgressListener(builder.build().getMessage());	
+	}
+	
+	public void setMessagingService(MessagingService m) {
+		messagingService = m;
+	}
+	
+	public void setGeneratedSlidesInfoHelper(GeneratedSlidesInfoHelper helper) {
+		generatedSlidesInfoHelper = helper;
+	}
+
+	public void sendCreatingTextFilesUpdateMessage(UploadedPresentation pres) {
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+		builder.messageKey(ConversionMessageConstants.GENERATING_TEXTFILES_KEY);
+		notifyProgressListener(builder.build().getMessage());	
+	}
+
+	public void sendCreatingSvgImagesUpdateMessage(UploadedPresentation pres) {
+		MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres);
+		builder.messageKey(ConversionMessageConstants.GENERATING_SVGIMAGES_KEY);
+		notifyProgressListener(builder.build().getMessage());
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java
new file mode 100755
index 0000000000..2bfe90bcdc
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java
@@ -0,0 +1,121 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.bigbluebutton.presentation.SupportedFileTypes;
+import org.bigbluebutton.presentation.TextFileCreator;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TextFileCreatorImp implements TextFileCreator {
+	private static Logger log = LoggerFactory.getLogger(TextFileCreatorImp.class);
+	
+	private String IMAGEMAGICK_DIR;
+	
+	@Override
+	public boolean createTextFiles(UploadedPresentation pres) {
+		boolean success = false;
+		File textfilesDir = determineTextfilesDirectory(pres.getUploadedFile());
+		if (! textfilesDir.exists())
+			textfilesDir.mkdir();
+		
+		cleanDirectory(textfilesDir);
+		
+		try {
+			success = generateTextFiles(textfilesDir, pres);
+	    } catch (InterruptedException e) {
+	    	log.warn("Interrupted Exception while generating thumbnails.");
+	        success = false;
+	    }
+	    
+	    //TODO: in case that it doesn't generated the textfile, we should create a textfile with some message
+	    // createUnavailableTextFile
+		
+		return success;
+	}
+
+	private boolean generateTextFiles(File textfilesDir, UploadedPresentation pres) throws InterruptedException {
+	 	boolean success = true;
+		String source = pres.getUploadedFile().getAbsolutePath();
+	 	String dest;
+	 	String COMMAND = "";
+	 	
+	 	if(SupportedFileTypes.isImageFile(pres.getFileType())){
+	 		dest = textfilesDir.getAbsolutePath() + File.separator + "slide-1.txt";
+	 		String text = "No text could be retrieved for the slide";
+	 		 
+            File file = new File(dest);
+            Writer writer = null;
+			try {
+				writer = new BufferedWriter(new FileWriter(file));
+				writer.write(text);
+			} catch (IOException e) {
+				log.error("Error: " + e.getMessage());
+				success = false;
+			} finally {
+				try {
+					writer.close();
+				} catch (IOException e) {
+					log.error("Error: " + e.getMessage());
+					success = false;
+				}
+			}
+            
+	 	}else{
+	 		dest = textfilesDir.getAbsolutePath() + File.separator + "slide-";
+	 		// sudo apt-get install xpdf-utils
+	 		for( int i = 1; i <= pres.getNumberOfPages(); i++){
+	 			COMMAND = IMAGEMAGICK_DIR + "/pdftotext -raw -nopgbrk -f "+ i +" -l " + i + " " + source + " " + dest + i + ".txt";
+	 			boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);
+	 			if (!done) {
+	 				success = false;
+	 		 		log.warn("Failed to create textfiles: " + COMMAND);
+	 		 		break;
+	 		 	}
+	 		}
+	 		
+	 	}
+
+		return success;		
+	}
+	
+	private File determineTextfilesDirectory(File presentationFile) {
+		return new File(presentationFile.getParent() + File.separatorChar + "textfiles");
+	}
+	
+	private void cleanDirectory(File directory) {	
+		File[] files = directory.listFiles();				
+		for (int i = 0; i < files.length; i++) {
+			files[i].delete();
+		}
+	}
+
+	public void setImageMagickDir(String imageMagickDir) {
+		IMAGEMAGICK_DIR = imageMagickDir;
+	}
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java
new file mode 100755
index 0000000000..23978e7e3b
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java
@@ -0,0 +1,163 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.presentation.imp;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+import org.bigbluebutton.presentation.SupportedFileTypes;
+import org.bigbluebutton.presentation.ThumbnailCreator;
+import org.bigbluebutton.presentation.UploadedPresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ThumbnailCreatorImp implements ThumbnailCreator {
+	private static Logger log = LoggerFactory.getLogger(ThumbnailCreatorImp.class);
+	
+	private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("(.+-thumb)-([0-9]+)(.png)");
+	
+	private String IMAGEMAGICK_DIR;
+	private String BLANK_THUMBNAIL;
+	
+	private static String TEMP_THUMB_NAME = "temp-thumb";
+	
+	public boolean createThumbnails(UploadedPresentation pres){
+		boolean success = false;
+	 	File thumbsDir = determineThumbnailDirectory(pres.getUploadedFile());
+	 	
+	 	if (! thumbsDir.exists())
+	 		thumbsDir.mkdir();
+	 		
+	 	cleanDirectory(thumbsDir);
+	 	
+		try {
+			success = generateThumbnails(thumbsDir, pres);
+	    } catch (InterruptedException e) {
+	    	log.warn("Interrupted Exception while generating thumbnails.");
+	        success = false;
+	    }
+	    
+	    // Create blank thumbnails for pages that failed to generate a thumbnail.
+	    createBlankThumbnails(thumbsDir, pres.getNumberOfPages());
+	    
+	    renameThumbnails(thumbsDir);
+	    
+	    return true;
+	}
+
+	private boolean generateThumbnails(File thumbsDir, UploadedPresentation pres) throws InterruptedException {
+	 	String source = pres.getUploadedFile().getAbsolutePath();
+	 	String dest;
+	 	String COMMAND = "";
+	 	if(SupportedFileTypes.isImageFile(pres.getFileType())){
+	 		dest = thumbsDir.getAbsolutePath() + File.separator + TEMP_THUMB_NAME + ".png";
+	 		COMMAND = IMAGEMAGICK_DIR + "/convert -thumbnail 150x150 " + source + " " + dest;
+	 	}else{
+	 		dest = thumbsDir.getAbsolutePath() + File.separator + "thumb-";
+	 		COMMAND = IMAGEMAGICK_DIR + "/gs -q -sDEVICE=pngalpha -dBATCH -dNOPAUSE -dNOPROMPT -dDOINTERPOLATE -dPDFFitPage -r16 -sOutputFile=" + dest +"%d.png " + source;
+	 	}
+
+	 	boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);
+
+	 	if (done) {
+	 		return true;
+	 	} else {
+			log.warn("Failed to create thumbnails: " + COMMAND);
+	 	}
+
+		return false;
+	}
+
+	private File determineThumbnailDirectory(File presentationFile) {
+		return new File(presentationFile.getParent() + File.separatorChar + "thumbnails");
+	}
+
+	private void renameThumbnails(File dir) {
+		/*
+		 * If more than 1 file, filename like 'temp-thumb-X.png' else filename is 'temp-thumb.png'
+		 */
+		if (dir.list().length > 1) {
+			File[] files = dir.listFiles();
+			Matcher matcher;
+			for (int i = 0; i < files.length; i++) {
+				matcher = PAGE_NUMBER_PATTERN.matcher(files[i].getAbsolutePath());
+	    		if (matcher.matches()) {
+	    			// Path should be something like 'c:/temp/bigluebutton/presname/thumbnails/temp-thumb-1.png'
+	    			// Extract the page number. There should be 4 matches.
+	    			// 0. c:/temp/bigluebutton/presname/thumbnails/temp-thumb-1.png
+					// 1. c:/temp/bigluebutton/presname/thumbnails/temp-thumb
+					// 2. 1 ---> what we are interested in
+					// 3. .png
+	    			// We are interested in the second match.
+				    int pageNum = Integer.valueOf(matcher.group(2).trim()).intValue();
+				    String newFilename = "thumb-" + (pageNum + 1) + ".png";
+				    File renamedFile = new File(dir.getAbsolutePath() + File.separator + newFilename);
+				    files[i].renameTo(renamedFile);
+	    		}
+			}
+		} else if (dir.list().length == 1) {
+			File oldFilename = new File(dir.getAbsolutePath() + File.separator + dir.list()[0]);
+			String newFilename = "thumb-1.png";
+			File renamedFile = new File(oldFilename.getParent() + File.separator + newFilename);
+			oldFilename.renameTo(renamedFile);
+		}
+	}
+	
+	private void createBlankThumbnails(File thumbsDir, int pageCount) {
+		File[] thumbs = thumbsDir.listFiles();
+		
+		if (thumbs.length != pageCount) {
+			for (int i = 0; i < pageCount; i++) {
+				File thumb = new File(thumbsDir.getAbsolutePath() + File.separator + TEMP_THUMB_NAME + "-" + i + ".png");
+				if (! thumb.exists()) {
+					log.info("Copying blank thumbnail for slide " + i);
+					copyBlankThumbnail(thumb);
+				}
+			}
+		}
+	}
+
+	private void copyBlankThumbnail(File thumb) {
+		try {
+			FileUtils.copyFile(new File(BLANK_THUMBNAIL), thumb);
+		} catch (IOException e) {
+			log.error("IOException while copying blank thumbnail.");
+		}
+	}
+
+	private void cleanDirectory(File directory) {
+		File[] files = directory.listFiles();
+		for (int i = 0; i < files.length; i++) {
+			files[i].delete();
+		}
+	}
+
+	public void setImageMagickDir(String imageMagickDir) {
+		IMAGEMAGICK_DIR = imageMagickDir;
+	}
+
+	public void setBlankThumbnail(String blankThumbnail) {
+		BLANK_THUMBNAIL = blankThumbnail;
+	}
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/ExpiredMeetingCleanupTimerTask.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/ExpiredMeetingCleanupTimerTask.java
new file mode 100755
index 0000000000..db8b5b8b2c
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/ExpiredMeetingCleanupTimerTask.java
@@ -0,0 +1,55 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.web.services;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.bigbluebutton.api.MeetingService;
+
+public class ExpiredMeetingCleanupTimerTask {
+
+	private MeetingService service;
+	private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
+	private long runEvery = 60000;
+
+	public void setMeetingService(MeetingService svc) {
+		this.service = svc;
+	}
+	
+	public void start() {
+		scheduledThreadPool.scheduleWithFixedDelay(new CleanupTask(), 60000, runEvery, TimeUnit.MILLISECONDS);		
+	}
+	
+	public void stop() {
+		scheduledThreadPool.shutdownNow();
+	}
+	
+	public void setRunEvery(long v) {
+		runEvery = v;
+	}
+	
+	private class CleanupTask implements Runnable {
+        public void run() {
+        	service.removeExpiredMeetings();
+        }
+    }
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/IStorageService.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/IStorageService.java
new file mode 100755
index 0000000000..c9de0936e0
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/IStorageService.java
@@ -0,0 +1,30 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.web.services;
+
+import org.bigbluebutton.api.domain.Poll;
+import java.util.Map;
+
+public interface IStorageService{
+	public String generatePollID(String meetingID);
+	public String generatePollAnswerID(String meetingID);
+	public void storePoll(Poll p);
+	public void storePollAnswers(String meetingID, String pollID, Map<String,String> answers);
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveMessage.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveMessage.java
new file mode 100755
index 0000000000..f6bf08c979
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveMessage.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.web.services;
+
+public interface KeepAliveMessage {
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePing.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePing.java
new file mode 100755
index 0000000000..3b9ff12daa
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePing.java
@@ -0,0 +1,7 @@
+package org.bigbluebutton.web.services;
+
+public class KeepAlivePing implements KeepAliveMessage {
+
+	public KeepAlivePing() {
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePong.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePong.java
new file mode 100755
index 0000000000..2f928bfa71
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAlivePong.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.web.services;
+
+public class KeepAlivePong implements KeepAliveMessage {
+
+	public final String system;
+	public final Long timestamp;
+	
+	public KeepAlivePong(String system, Long timestamp) {
+		this.system = system;
+		this.timestamp = timestamp;
+	}
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveService.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveService.java
new file mode 100755
index 0000000000..d0ee4734b5
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/KeepAliveService.java
@@ -0,0 +1,169 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.web.services;
+
+import org.bigbluebutton.api.messaging.MessageListener;
+import org.bigbluebutton.api.messaging.MessagingService;
+import org.bigbluebutton.api.messaging.MessagingConstants;
+import org.bigbluebutton.api.messaging.RedisMessagingService;
+import org.bigbluebutton.api.messaging.messages.IMessage;
+import org.bigbluebutton.api.messaging.messages.KeepAliveReply;
+import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.google.gson.Gson;
+
+public class KeepAliveService implements MessageListener {
+	private static Logger log = LoggerFactory.getLogger(KeepAliveService.class);
+	private final String KEEP_ALIVE_REQUEST = "KEEP_ALIVE_REQUEST";
+	private MessagingService service;
+	private long runEvery = 10000;
+	private int maxLives = 5;
+	private KeepAliveTask task = new KeepAliveTask();
+	private volatile boolean processMessages = false;
+
+	volatile boolean available = false;
+	
+	private static final Executor msgSenderExec = Executors.newFixedThreadPool(1);
+	private static final Executor runExec = Executors.newFixedThreadPool(1);
+	
+	private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
+	
+	private BlockingQueue<KeepAliveMessage> messages = new LinkedBlockingQueue<KeepAliveMessage>();
+	
+	private Long lastKeepAliveMessage = 0L;
+	
+	private final String SYSTEM = "BbbWeb";
+	
+	public void start() {	
+		scheduledThreadPool.scheduleWithFixedDelay(task, 5000, runEvery, TimeUnit.MILLISECONDS);
+		processKeepAliveMessage();
+	}
+	
+	public void stop() {
+		processMessages = false;
+		scheduledThreadPool.shutdownNow();
+	}
+	
+	public void setRunEvery(long v) {
+		runEvery = v * 1000;
+	}
+
+	public void setMessagingService(MessagingService service){
+		this.service = service;
+	}
+	
+	class KeepAliveTask implements Runnable {
+    public void run() {
+     	KeepAlivePing ping = new KeepAlivePing();
+     	queueMessage(ping);
+    }
+  }
+
+  public boolean isDown(){
+  	return !available;
+  }
+    
+  private void queueMessage(KeepAliveMessage msg) {
+		  messages.add(msg);
+  }
+    
+  private void processKeepAliveMessage() {
+  	processMessages = true;
+  	Runnable sender = new Runnable() {
+  		public void run() {
+  			while (processMessages) {
+  				KeepAliveMessage message;
+  				try {
+  					message = messages.take();
+  					processMessage(message);	
+  				} catch (InterruptedException e) {
+  					// TODO Auto-generated catch block
+  					e.printStackTrace();
+  				}	catch (Exception e) {
+  					log.error("Catching exception [{}]", e.toString());
+  				}
+  			}
+  		}
+  	};
+  	msgSenderExec.execute(sender);		
+  } 
+  	
+  private void processMessage(final KeepAliveMessage msg) {
+  	Runnable task = new Runnable() {
+  		public void run() {
+	  	  	if (msg instanceof KeepAlivePing) {
+	  	  		processPing((KeepAlivePing) msg);
+	  	  	} else if (msg instanceof KeepAlivePong) {
+	  	  		processPong((KeepAlivePong) msg);
+	  	  	}  			
+  		}
+  	};
+  	
+    runExec.execute(task);
+  }
+  	
+  private void processPing(KeepAlivePing msg) {
+	  service.sendKeepAlive(SYSTEM, System.currentTimeMillis());
+	  
+	  if (lastKeepAliveMessage != 0 && (System.currentTimeMillis() - lastKeepAliveMessage > 30000)) {
+		  log.error("BBB Web pubsub error!");
+	   		// BBB-Apps has gone down. Mark it as unavailable. (ralam - april 29, 2014)
+	   		available = false;
+	  }		
+  }
+  	
+  private void processPong(KeepAlivePong msg) {
+	  if (lastKeepAliveMessage != 0 && !available) {
+		  log.error("BBB Web pubsub recovered!");
+	  }
+	  
+	  lastKeepAliveMessage = System.currentTimeMillis(); 
+	  available = true;
+  }
+  
+  private void handleKeepAliveReply(String system, Long timestamp) {
+	  if (system.equals("BbbWeb")) {
+		   	KeepAlivePong pong = new KeepAlivePong(system, timestamp);
+		   	queueMessage(pong);		  
+	  }
+
+  }
+  
+	@Override
+  public void handle(IMessage message) {
+		if (message instanceof KeepAliveReply) {
+			KeepAliveReply msg = (KeepAliveReply) message;
+			handleKeepAliveReply(msg.system, msg.timestamp);
+		}
+  }
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java
new file mode 100755
index 0000000000..f1e3b5446d
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java
@@ -0,0 +1,90 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.web.services;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import java.util.HashMap;
+import java.util.Map;
+import org.bigbluebutton.api.domain.Poll;
+
+public class RedisStorageService implements IStorageService{
+	JedisPool jedisPool;
+
+	private final String SEPARATOR = ":";
+	private final String ID_SEED = "nextID";
+
+	/* Meeting Patterns */
+	private final String MEETING = "meeting";
+	private final String POLL = "poll";
+	private final String POLL_ANSWER = "answer";
+	private final String POLL_RESULTS = "results";
+
+	/*
+meeting:<id>:poll:list [1,2,3] <-- list
+meeting:<id>:poll:<pollid> title, date <-- hash
+meeting:<id>:poll:<pollid>:answer:list [1,2,3] <-- list
+meeting:<id>:poll:<pollid>:answer:<answerid> answertext <-- key/value
+
+meeting:<id>:poll:<pollid>:answer:<answerid>:results [<userid>|1] <-- Set
+	*/
+
+	public String generatePollID(String meetingID){
+		Jedis jedis = (Jedis) jedisPool.getResource();
+		String pattern = getPollRedisPattern(meetingID);
+		String pollID = Long.toString(jedis.incr(pattern + SEPARATOR + ID_SEED));
+		jedisPool.returnResource(jedis);
+		return pollID;
+	}
+
+	public String generatePollAnswerID(String meetingID){
+		Jedis jedis = jedisPool.getResource();
+		String pattern = getPollRedisPattern(meetingID);
+		String pollID = Long.toString(jedis.incr(pattern + SEPARATOR + POLL_ANSWER + SEPARATOR + ID_SEED));
+		jedisPool.returnResource(jedis);
+		return pollID;
+	}
+
+	public void storePoll(Poll p){
+		Jedis jedis = jedisPool.getResource();
+		String pattern = getPollRedisPattern(p.getMeetingID());
+
+		HashMap<String,String> pollMap = p.toMap();
+		jedis.hmset(pattern + SEPARATOR + p.getPollID(), pollMap);
+		jedisPool.returnResource(jedis);
+	}
+
+	public void storePollAnswers(String meetingID, String pollID, Map<String,String> answers){
+		Jedis jedis = jedisPool.getResource();
+		String pattern = getPollRedisPattern(meetingID);
+
+		//HashMap<String,String> pollMap = p.toMap();
+		//jedis.hmset(pattern + SEPARATOR + p.getPollID + SEPARATOR + POLL_ANSWER + SEPARATOR + ID_SEED, pollMap);
+		//jedisPool.returnResource(jedis);	
+	}
+
+	private String getPollRedisPattern(String meetingID){
+		return MEETING + SEPARATOR + meetingID + SEPARATOR + POLL;
+	}
+
+	public void setJedisPool(JedisPool jedisPool){
+		this.jedisPool = jedisPool;
+	}
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java
new file mode 100755
index 0000000000..9525662b48
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java
@@ -0,0 +1,55 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program 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.0 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, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+package org.bigbluebutton.web.services;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.bigbluebutton.api.MeetingService;
+
+public class RegisteredUserCleanupTimerTask {
+
+	private MeetingService service;
+	private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
+	private long runEvery = 60000;
+
+	public void setMeetingService(MeetingService svc) {
+		this.service = svc;
+	}
+
+	public void start() {
+		scheduledThreadPool.scheduleWithFixedDelay(new CleanupTask(), 60000, runEvery, TimeUnit.MILLISECONDS);
+	}
+
+	public void stop() {
+		scheduledThreadPool.shutdownNow();
+	}
+
+	public void setRunEvery(long v) {
+		runEvery = v;
+	}
+
+	private class CleanupTask implements Runnable {
+        public void run() {
+            service.purgeRegisteredUsers();
+        }
+    }
+}
\ No newline at end of file
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunServer.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunServer.java
new file mode 100755
index 0000000000..272c0f1bab
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunServer.java
@@ -0,0 +1,10 @@
+package org.bigbluebutton.web.services.turn;
+
+public class StunServer {
+
+  public final String url;
+  
+  public StunServer(String url) {
+    this.url = url;
+  }
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunTurnService.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunTurnService.java
new file mode 100755
index 0000000000..656f14151c
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/StunTurnService.java
@@ -0,0 +1,42 @@
+package org.bigbluebutton.web.services.turn;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class StunTurnService {
+  private static Logger log = LoggerFactory.getLogger(StunTurnService.class);
+
+  private Set<StunServer> stunServers;
+  private Set<TurnServer> turnServers;
+
+  public Set<StunServer> getStunServers() {
+    log.info("\nStunTurnService::getStunServers \n");
+    return stunServers;
+  }
+
+  public Set<TurnEntry> getStunAndTurnServersFor(String userId) {
+    log.info("\nStunTurnService::getStunAndTurnServersFor " + userId + "\n");
+    Set<TurnEntry> turns = new HashSet<TurnEntry>();
+
+    for (TurnServer ts : turnServers) {
+      TurnEntry entry = ts.generatePasswordFor(userId);
+      if (entry != null) {
+        turns.add(entry);
+      }
+    }
+    
+    return turns;
+  }
+
+  public void setStunServers(Set<StunServer> stuns) {
+    stunServers = stuns;
+  }
+
+  public void setTurnServers(Set<TurnServer> turns) {
+    turnServers = turns;
+  }
+
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnEntry.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnEntry.java
new file mode 100755
index 0000000000..696fd0dedd
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnEntry.java
@@ -0,0 +1,33 @@
+package org.bigbluebutton.web.services.turn;
+
+public class TurnEntry {
+
+  public final String username;
+  public final String url;
+  public final String password;
+  public final int ttl;
+  
+  public TurnEntry(String username, String password, int ttl, String url) {
+    this.username = username;
+    this.url = url;
+    this.password = password;
+    this.ttl = ttl;
+  }
+/*  
+  public String getUsername() {
+    return username;
+  }
+  
+  public String getUrl() {
+    return url;
+  }
+  
+  public String getPassord() {
+    return password;
+  }
+  
+  public int getTtl() {
+    return ttl;
+  }
+  */
+}
diff --git a/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnServer.java b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnServer.java
new file mode 100755
index 0000000000..6932b2cfc3
--- /dev/null
+++ b/bbb-web-api/src/main/java/org/bigbluebutton/web/services/turn/TurnServer.java
@@ -0,0 +1,77 @@
+package org.bigbluebutton.web.services.turn;
+
+import java.security.SignatureException;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+
+public class TurnServer {
+
+  private final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
+  private final String COLON = ":";
+  
+  private final String secretKey;
+  private final String url;
+  private final int ttl;
+
+  public TurnServer(String secretKey, String url, int ttl) {
+    this.secretKey = secretKey;
+    this.url = url;
+    this.ttl = ttl;
+  }
+
+  public TurnEntry generatePasswordFor(String userId) {
+    TurnEntry turn = null;
+    
+    try {
+      long expiryTime = System.currentTimeMillis() / 1000 + ttl;
+      String username = expiryTime + COLON + userId;
+      String password = calculateRFC2104HMAC(username, secretKey);
+      turn = new TurnEntry(username, password, ttl, url);
+    } catch (SignatureException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+
+    return turn;
+  }
+
+
+
+
+  /**
+   * Computes RFC 2104-compliant HMAC signature.
+   * * @param data
+   * The data to be signed.
+   * @param key
+   * The signing key.
+   * @return
+   * The Base64-encoded RFC 2104-compliant HMAC signature.
+   * @throws
+   * java.security.SignatureException when signature generation fails
+   */
+  private String calculateRFC2104HMAC(String data, String key)
+      throws java.security.SignatureException
+      {
+    String result;
+    try {
+
+      // get an hmac_sha1 key from the raw key bytes
+      SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
+
+      // get an hmac_sha1 Mac instance and initialize with the signing key
+      Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
+      mac.init(signingKey);
+
+      // compute the hmac on input data bytes
+      byte[] rawHmac = mac.doFinal(data.getBytes());
+
+      // base64-encode the hmac
+      result = new String(Base64.encodeBase64(rawHmac));
+
+    } catch (Exception e) {
+      throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
+    }
+    return result;
+      }
+}
diff --git a/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings-empty.ftl b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings-empty.ftl
new file mode 100755
index 0000000000..3b4793c822
--- /dev/null
+++ b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings-empty.ftl
@@ -0,0 +1,11 @@
+<#-- GET_RECORDINGS FreeMarker XML template -->
+<#compress>
+<response>
+  <#-- Where code is a 'SUCCESS' or 'FAILED' String -->
+  <returncode>${code}</returncode>
+  <recordings>
+  </recordings>
+  <messageKey>noRecordings</messageKey>
+  <message>There are not recordings for the meetings</message>
+</response>
+</#compress>
diff --git a/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings.ftl b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings.ftl
new file mode 100755
index 0000000000..875267ff44
--- /dev/null
+++ b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/get-recordings.ftl
@@ -0,0 +1,41 @@
+<#-- GET_RECORDINGS FreeMarker XML template -->
+<#compress>
+<response>
+  <#-- Where code is a 'SUCCESS' or 'FAILED' String -->
+  <returncode>${code}</returncode>
+  <recordings>
+  <#-- Where recs is a String -> Recording HashMap -->
+  <#list recs as r>
+    <recording>
+      <recordID>${r.getId()}</recordID>
+      <meetingID><#if r.getMeetingID()?? && r.getMeetingID() != "">${r.getMeetingID()?html}</#if></meetingID>
+      <name><#if r.getName()?? && r.getName() != ""><![CDATA[${r.getName()}]]></#if></name>
+      <published>${r.isPublished()?string}</published>
+      <state>${r.getState()?string}</state>
+      <startTime><#if r.getStartTime()?? && r.getStartTime() != "">${r.getStartTime()}</#if></startTime>
+      <endTime><#if r.getEndTime()?? && r.getEndTime() != "">${r.getEndTime()}</#if></endTime>
+      <#assign m = r.getMetadata()>
+      <metadata>
+      <#list m?keys as prop>
+        <${prop}><![CDATA[${m[prop]}]]></${prop}>
+      </#list>
+      </metadata>
+      <playback>
+        <#if r.getPlaybacks()??>
+        <#list r.getPlaybacks() as p>
+          <#if p?? && p.getFormat()??>
+          <format>
+            <type>${p.getFormat()}</type>
+            <url>${p.getUrl()}</url>
+            <length>${p.getLength()}</length>
+            <#-- Missing p.getExtensions() -->
+          </format>
+          </#if>
+        </#list>
+        </#if>
+      </playback>
+    </recording>
+  </#list>
+  </recordings>
+</response>
+</#compress>
\ No newline at end of file
diff --git a/bbb-web-api/src/main/webapp/WEB-INF/freemarker/invalid-response.ftl b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/invalid-response.ftl
new file mode 100755
index 0000000000..a8d20ff3df
--- /dev/null
+++ b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/invalid-response.ftl
@@ -0,0 +1,8 @@
+<#-- GET_RECORDINGS FreeMarker XML template -->
+<#compress>
+<response>
+  <returncode>${returnCode}</returncode>
+  <messageKey>${messageKey}</messageKey>
+  <message>${message}</message>
+</response>
+</#compress>
diff --git a/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference-details.ftl b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference-details.ftl
new file mode 100755
index 0000000000..e3b73440df
--- /dev/null
+++ b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference-details.ftl
@@ -0,0 +1,55 @@
+<#-- GET_RECORDINGS FreeMarker XML template -->
+<#compress>
+<response>
+  <#-- Where code is a 'SUCCESS' or 'FAILED' String -->
+  <returncode>${returnCode}</returncode>
+  <meetingName>${meeting.getName()}</meetingName>
+  <isBreakout>${meeting.isBreakout()?c}</isBreakout>
+  <meetingID>${meeting.getExternalId()}</meetingID>
+  <internalMeetingID>${meeting.getInternalId()}</internalMeetingID>
+  <createTime>${meeting.getCreateTime()}</createTime>
+  <createDate>${createdOn}</createDate>
+  <voiceBridge>${meeting.getTelVoice()}</voiceBridge>
+  <dialNumber>${meeting.getDialNumber()}</dialNumber>
+  <attendeePW>${meeting.getViewerPassword()}</attendeePW>
+  <moderatorPW>${meeting.getModeratorPassword()}</moderatorPW>
+  <running>${meeting.isRunning()?c}</running>
+  <duration>${meeting.getDuration()}</duration>
+  <hasUserJoined>${meeting.hasUserJoined()?c}</hasUserJoined>
+  <recording>${meeting.isRecord()?c}</recording>
+  <hasBeenForciblyEnded>${meeting.isForciblyEnded()?c}</hasBeenForciblyEnded>
+  <startTime>${meeting.getStartTime()}</startTime>
+  <endTime>${meeting.getEndTime()}</endTime>
+  <participantCount>${meeting.getNumUsers()}</participantCount>
+  <listenerCount>${meeting.getNumListenOnly()}</listenerCount>
+  <voiceParticipantCount>${meeting.getNumVoiceJoined()}</voiceParticipantCount>
+  <videoCount>${meeting.getNumVideos()}</videoCount>
+  <maxUsers>${meeting.getMaxUsers()}<maxUsers>
+  <moderatorCount>${meeting.getNumModerators()}</moderatorCount>
+  <attendees>
+  <#list meeting.getUsers() as att>
+    <attendee>
+        <userID>${att.getInternalUserId()}</userID>
+        <fullName>${att.getFullname()}</fullName>
+        <role>${att.getRole()}</role>
+        <isPresenter>${att.isPresenter()}</isPresenter>
+        <isListeningOnly>${att.isListeningOnly()}</isListeningOnly>
+        <hasJoinedVoice>${att.isVoiceJoined()}</hasJoinedVoice>
+        <hasVideo>${att.hasVideo()}</hasVideo>
+        <#assign ucd = r.getUserCustomData(att.getExternalUserId())>
+        <customdata>
+          <#list ucd?keys as prop>
+            <${prop}><![CDATA[${ucd[prop]}]]></${prop}>
+          </#list>
+        </customdata>
+  </attendees>
+  <#assign m = r.getMetadata()>
+  <metadata>
+  <#list m?keys as prop>
+     <${prop}><![CDATA[${m[prop]}]]></${prop}>
+  </#list>
+  </metadata>
+  <messageKey>${messageKey}</messageKey>
+  <message>${message}</message>
+</response>
+</#compress>
diff --git a/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference.ftl b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference.ftl
new file mode 100755
index 0000000000..216bf15833
--- /dev/null
+++ b/bbb-web-api/src/main/webapp/WEB-INF/freemarker/respond-with-conference.ftl
@@ -0,0 +1,19 @@
+<#-- GET_RECORDINGS FreeMarker XML template -->
+<#compress>
+<response>
+  <#-- Where code is a 'SUCCESS' or 'FAILED' String -->
+  <returncode>${returnCode}</returncode>
+  <meetingID>${meeting.getExternalId()}</meetingID>
+  <attendeePW>${meeting.getViewerPassword()}</attendeePW>
+  <moderatorPW>${meeting.getModeratorPassword()}</moderatorPW>
+  <createTime>${meeting.getCreateTime()}</createTime>
+  <voiceBridge>${meeting.getTelVoice()}</voiceBridge>
+  <dialNumber>${meeting.getDialNumber()}</dialNumber>
+  <createDate>${createdOn}</createDate>
+  <hasUserJoined>${meeting.hasUserJoined()?c}</hasUserJoined>
+  <duration>${meeting.getDuration()?c}</duration>
+  <hasBeenForciblyEnded>${meeting.isForciblyEnded()?c}</hasBeenForciblyEnded>
+  <messageKey>${messageKey}</messageKey>
+  <message>${message}</message>
+</response>
+</#compress>
-- 
GitLab