/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.modules.gotomeeting.manager; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.json.JSONObject; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupRef; import org.olat.modules.gotomeeting.GoToMeeting; import org.olat.modules.gotomeeting.GoToMeetingManager; import org.olat.modules.gotomeeting.GoToMeetingModule; import org.olat.modules.gotomeeting.GoToOrganizer; import org.olat.modules.gotomeeting.GoToRegistrant; import org.olat.modules.gotomeeting.model.GoToError; import org.olat.modules.gotomeeting.model.GoToErrorG2T; import org.olat.modules.gotomeeting.model.GoToErrors; import org.olat.modules.gotomeeting.model.GoToMeetingImpl; import org.olat.modules.gotomeeting.model.GoToOrganizerG2T; import org.olat.modules.gotomeeting.model.GoToRecordingsG2T; import org.olat.modules.gotomeeting.model.GoToRegistrantG2T; import org.olat.modules.gotomeeting.model.GoToTrainingG2T; import org.olat.modules.gotomeeting.model.GoToType; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Initial date: 21.03.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ @Service public class GoToMeetingManagerImpl implements GoToMeetingManager { private static final OLog log = Tracing.createLoggerFor(GoToMeetingManagerImpl.class); private String directLoginUrl = "https://api.citrixonline.com/oauth/access_token"; private String gotoTrainingUrl = "https://api.citrixonline.com/G2T/rest"; @Autowired private DB dbInstance; @Autowired private GoToMeetingDAO meetingDao; @Autowired private GoToOrganizerDAO organizerDao; @Autowired private GoToRegistrantDAO registrantDao; @Autowired private GoToMeetingModule goToMeetingModule; @Override public List<GoToOrganizer> getOrganizersFor(Identity identity) { return organizerDao.getOrganizersFor(identity); } @Override public boolean addOrganizer(String name, String username, String password, Identity owner, GoToError error) { GoToOrganizerG2T organizerVo = directLogin(username, password, error); if(organizerVo != null) { GoToOrganizer organizer = organizerDao.loadOrganizerByUsername(username); if(organizer == null) { organizerDao.createOrganizer(name, username, organizerVo.getAccessToken(), organizerVo.getOrganizerKey(), organizerVo.getFirstName(), organizerVo.getLastName(), organizerVo.getEmail(), organizerVo.getAccountKey(), organizerVo.getExpiresIn(), owner); } else { organizerDao.updateOrganizer(organizer, name, organizerVo.getAccessToken(), organizerVo.getOrganizerKey(), organizerVo.getFirstName(), organizerVo.getLastName(), organizerVo.getEmail(), organizerVo.getAccountKey(), organizerVo.getExpiresIn()); } return true; } return false; } @Override public boolean removeOrganizer(GoToOrganizer organizer) { if(meetingDao.countMeetingsOrganizedBy(organizer) == 0) { organizerDao.deleteOrganizer(organizer); return true; } return false; } public boolean checkOrganizerAvailability(GoToOrganizer organizer, Date start, Date end) { List<GoToMeeting> meetings = meetingDao.getMeetingsOverlap(GoToType.training, organizer, start, end); return meetings.isEmpty(); } @Override public GoToMeeting scheduleTraining(GoToOrganizer organizer, String name, String externalId, String description, Date start, Date end, RepositoryEntry resourceOwner, String subIdentifier, BusinessGroup businessGroup, GoToError error) { //GoToMeeting scheduledMeeting = meetingDao.createTraining(name, externalId, description, UUID.randomUUID().toString(), start, end, organizer, resourceOwner, subIdentifier, businessGroup); GoToMeeting scheduledMeeting = null; try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings"; HttpPost post = new HttpPost(url); post.addHeader("Accept", "application/json"); post.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); post.addHeader("Content-type", "application/json"); String timeZoneId = goToMeetingModule.getGoToTimeZoneId(); JSONObject trainingJson = GoToJsonUtil .training(Long.parseLong(organizer.getOrganizerKey()), name, description, timeZoneId, start, end); String objectStr = trainingJson.toString(); post.setEntity(new StringEntity(objectStr, ContentType.APPLICATION_JSON)); HttpResponse response = httpClient.execute(post); int status = response.getStatusLine().getStatusCode(); if(status == 201) {//created String trainingKey = EntityUtils.toString(response.getEntity()); trainingKey = trainingKey.replace("\"", ""); scheduledMeeting = meetingDao.createTraining(name, externalId, description, trainingKey, start, end, organizer, resourceOwner, subIdentifier, businessGroup); dbInstance.commit(); } else { logGoToError("scheduleTraining", response, error); } } catch(Exception e) { log.error("", e); } return scheduledMeeting; } @Override public GoToMeeting updateTraining(GoToMeeting meeting, String name, String description, Date start, Date end, GoToError error) { //reload the training from GoTo GoToTrainingG2T training = getTraining(meeting, error); if(training != null && !error.hasError()) { GoToMeetingImpl meetingImpl = (GoToMeetingImpl)meeting; if(!training.getName().equals(name) || (StringHelper.containsNonWhitespace(description) && !"-".equals(description) && !description.equals(training.getDescription()))) { //update name and description updateNameDescription(meetingImpl, training, name, description, error); } if(!error.hasError()) { if((start != null && start.compareTo(training.getStart()) != 0) || (end != null && end.compareTo(training.getEnd()) != 0)) { updateStartEnd(meetingImpl, start, end, error); } } if(!error.hasError()) { meeting = meetingDao.update(meetingImpl); } } return meeting; } private void updateStartEnd(GoToMeetingImpl meeting, Date start, Date end, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey() + "/times"; HttpPut put = new HttpPut(url); put.addHeader("Accept", "application/json"); put.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); put.addHeader("Content-type", "application/json"); String payload = GoToJsonUtil.trainingTimes(goToMeetingModule.getGoToTimeZoneId(), start, end).toString(); put.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON)); HttpResponse response = httpClient.execute(put); int status = response.getStatusLine().getStatusCode(); if(status == 200) {//created EntityUtils.consume(response.getEntity()); meeting.setStartDate(start); meeting.setEndDate(end); } else { logGoToError("updateStartEnd", response, error); } } catch(Exception e) { log.error("", e); } } private void updateNameDescription(GoToMeetingImpl meeting, GoToTrainingG2T training, String name, String description, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey() + "/nameDescription"; HttpPut put = new HttpPut(url); put.addHeader("Accept", "application/json"); put.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); put.addHeader("Content-type", "application/json"); if(!StringHelper.containsNonWhitespace(description) || "-".equals(description)) { description = training.getDescription(); } String payload = GoToJsonUtil.trainingNameDescription(name, description).toString(); put.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON)); HttpResponse response = httpClient.execute(put); int status = response.getStatusLine().getStatusCode(); if(status == 204) {//created EntityUtils.consume(response.getEntity()); meeting.setName(name); meeting.setDescription(description); } else { logGoToError("updateNameDescription", response, error); } } catch(Exception e) { log.error("", e); } } /** * Error code: 400 (Bad Request), 403 (Forbidden), 404 (Not Found), 409 (Conflict) */ @Override public GoToRegistrant registerTraining(GoToMeeting meeting, Identity trainee, GoToError error) { GoToRegistrant registrant = registrantDao.getRegistrant(meeting, trainee); if(registrant == null) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey() + "/registrants"; HttpPost post = new HttpPost(url); post.addHeader("Accept", "application/json"); post.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); post.addHeader("Content-type", "application/json"); String traineeJson = GoToJsonUtil.registrant(trainee).toString(); post.setEntity(new StringEntity(traineeJson, ContentType.APPLICATION_JSON)); HttpResponse response = httpClient.execute(post); int status = response.getStatusLine().getStatusCode(); if(status == 201) {//created String content = EntityUtils.toString(response.getEntity()); GoToRegistrantG2T registrantVo = GoToJsonUtil.parseAddRegistrant(content); registrant = registrantDao.createRegistrant(meeting, trainee, registrantVo.getRegistrantKey(), registrantVo.getJoinUrl(), registrantVo.getConfirmationUrl()); } else if(status == 409) { String content = EntityUtils.toString(response.getEntity()); GoToErrorG2T errorVo = GoToJsonUtil.parseError(content); if(errorVo.getErrorCode() == GoToErrors.DuplicateRegistrant && StringHelper.containsNonWhitespace(errorVo.getRegistrantKey())) { //already registrate but not in OpenOLAT GoToRegistrantG2T registrantVo = getRegistrant(errorVo.getRegistrantKey(), meeting, error); registrant = registrantDao.createRegistrant(meeting, trainee, registrantVo.getRegistrantKey(), registrantVo.getJoinUrl(), registrantVo.getConfirmationUrl()); } else { logGoToError("registerTraining", status, content, error); } } else { logGoToError("registerTraining", response, error); } } catch(Exception e) { log.error("", e); } } return registrant; } private GoToRegistrantG2T getRegistrant(String registrantKey, GoToMeeting meeting, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey() + "/registrants/" + registrantKey; HttpGet get = new HttpGet(url); get.addHeader("Accept", "application/json"); get.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); get.addHeader("Content-type", "application/json"); HttpResponse response = httpClient.execute(get); int status = response.getStatusLine().getStatusCode(); if(status == 200) { String content = EntityUtils.toString(response.getEntity()); GoToRegistrantG2T registrantVo = GoToJsonUtil.parseAddRegistrant(content); return registrantVo; } else { logGoToError("getRegistrant", response, error); return null; } } catch(Exception e) { log.error("", e); return null; } } private void logGoToError(String method, HttpResponse response, GoToError error) throws IOException { int status = response.getStatusLine().getStatusCode(); error.setErrorCode(status); String responseString = EntityUtils.toString(response.getEntity()); try { GoToErrorG2T errorVo = GoToJsonUtil.parseError(responseString); if(errorVo != null) { error.setError(errorVo.getErrorCode()); error.setDescription(errorVo.getDescription()); } } catch (Exception e) { log.error("", e); } finally { EntityUtils.consumeQuietly(response.getEntity()); log.error(method + " return " + status + ": " + responseString); } } private void logGoToError(String method, int status, String responseString, GoToError error) { error.setErrorCode(status); log.error(method + " return " + status + ": " + responseString); } @Override public List<GoToMeeting> getAllMeetings() { return meetingDao.getMeetings(); } @Override public List<GoToMeeting> getMeetings(GoToType type, RepositoryEntryRef entry, String subIdent, BusinessGroupRef businessGroup) { return meetingDao.getMeetings(type, entry, subIdent, businessGroup); } @Override public GoToMeeting getMeetingByKey(Long meetingKey) { return meetingDao.loadMeetingByKey(meetingKey); } @Override public GoToMeeting getMeetingByExternalId(String externalId) { return meetingDao.loadMeetingByExternalId(externalId); } @Override public GoToMeeting getMeeting(GoToMeeting meeting, GoToError error) { GoToMeeting reloadMeeting = meetingDao.loadMeetingByKey(meeting.getKey()); if(reloadMeeting != null) { GoToTrainingG2T trainingVo = getTraining(meeting, error); if(trainingVo == null) { log.error("Training not found"); } } return reloadMeeting; } private GoToTrainingG2T getTraining(GoToMeeting meeting, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey(); HttpGet get = new HttpGet(url); get.addHeader("Accept", "application/json"); get.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); get.addHeader("Content-type", "application/json"); HttpResponse response = httpClient.execute(get); int status = response.getStatusLine().getStatusCode(); if(status == 200) {//deleted String content = EntityUtils.toString(response.getEntity()); GoToTrainingG2T trainingVo = GoToJsonUtil.parseTraining(content); return trainingVo; } else { logGoToError("getTraining", response, error); return null; } } catch(Exception e) { log.error("", e); return null; } } @Override public String startTraining(GoToMeeting meeting, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/trainings/" + meeting.getMeetingKey() + "/start"; HttpGet get = new HttpGet(url); get.addHeader("Accept", "application/json"); get.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); get.addHeader("Content-type", "application/json"); HttpResponse response = httpClient.execute(get); int status = response.getStatusLine().getStatusCode(); if(status == 200) {//deleted String content = EntityUtils.toString(response.getEntity()); String startUrl = GoToJsonUtil.parseHostUrl(content); return startUrl; } else { logGoToError("startTraining", response, error); return null; } } catch(Exception e) { log.error("", e); return null; } } @Override public List<GoToRegistrant> getRegistrants(IdentityRef identity, RepositoryEntryRef entry, String subIdent, BusinessGroupRef businessGroup) { return registrantDao.getRegistrants(identity, entry, subIdent, businessGroup); } @Override public GoToRegistrant getRegistrant(GoToMeeting meeting, IdentityRef trainee) { return registrantDao.getRegistrant(meeting, trainee); } @Override public List<GoToRecordingsG2T> getRecordings(GoToMeeting meeting, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/trainings/" + meeting.getMeetingKey() + "/recordings"; HttpGet get = new HttpGet(url); get.addHeader("Accept", "application/json"); get.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); get.addHeader("Content-type", "application/json"); HttpResponse response = httpClient.execute(get); int status = response.getStatusLine().getStatusCode(); if(status == 200) {//deleted String content = EntityUtils.toString(response.getEntity()); List<GoToRecordingsG2T> recordings = GoToJsonUtil.parseRecordings(content); return recordings; } else { logGoToError("getRecordings", response, error); return null; } } catch(Exception e) { log.error("", e); return null; } } @Override public boolean delete(GoToMeeting meeting) { GoToError error = new GoToError(); if(deleteTraining(meeting, error)) { meetingDao.delete(meeting); return true; } else if(error.getError() == GoToErrors.NoSuchTraining || error.getError() == GoToErrors.InvalidRequest) { //clean up our database meetingDao.delete(meeting); return true; } else { //do nothing return false; } } private boolean deleteTraining(GoToMeeting meeting, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { GoToOrganizer organizer = meeting.getOrganizer(); String url = gotoTrainingUrl + "/organizers/" + organizer.getOrganizerKey() + "/trainings/" + meeting.getMeetingKey(); HttpDelete delete = new HttpDelete(url); delete.addHeader("Accept", "application/json"); delete.addHeader("Authorization", "OAuth oauth_token=" + organizer.getAccessToken()); delete.addHeader("Content-type", "application/json"); HttpResponse response = httpClient.execute(delete); int status = response.getStatusLine().getStatusCode(); if(status == 204) {//deleted return true; } else if (status == 404 || status == 400) { String content = EntityUtils.toString(response.getEntity()); GoToErrorG2T errorVo = GoToJsonUtil.parseError(content); if(errorVo.getErrorCode() == GoToErrors.NoSuchTraining || errorVo.getErrorCode() == GoToErrors.InvalidRequest) { error.setError(errorVo.getErrorCode()); error.setDescription(errorVo.getDescription()); } else { log.error("deleteTraining return " + status + ": " + content); } } else { logGoToError("deleteTraining", response, error); } return false; } catch(Exception e) { log.error("", e); return false; } } @Override public void deleteAll(RepositoryEntryRef entry, String subIdent, BusinessGroupRef businessGroup) { List<GoToMeeting> trainings = meetingDao.getMeetings(GoToType.training, entry, subIdent, businessGroup); for(GoToMeeting training:trainings) { delete(training); } } /** * curl -X POST -H "Accept:application/json" * -H "Content-Type: application/x-www-form-urlencoded" "https://api.citrixonline.com/oauth/access_token" * -d 'grant_type=password&user_id=test@test.com&password=xyz&client_id={consumerKey}' * * * { * "access_token":"RlUe11faKeyCWxZToK3nk0uTKAL", * "expires_in":"30758399", * "refresh_token":"d1cp20yB3hrFAKeTokenTr49EZ34kTvNK", * "organizer_key":"8439885694023999999", * "account_key":"9999982253621659654", * "account_type":"", * "firstName":"Mahar", * "lastName":"Singh", * "email":"mahar.singh@singhSong.com", * "platform":"GLOBAL", * "version":"2", * } */ private GoToOrganizerG2T directLogin(String username, String password, GoToError error) { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String consumerKey = goToMeetingModule.getTrainingConsumerKey(); HttpPost post = new HttpPost(directLoginUrl); post.addHeader("Accept", "application/json"); List<NameValuePair> urlParameters = new ArrayList<>(4); urlParameters.add(new BasicNameValuePair("grant_type", "password")); urlParameters.add(new BasicNameValuePair("user_id", username)); urlParameters.add(new BasicNameValuePair("password", password)); urlParameters.add(new BasicNameValuePair("client_id", consumerKey)); post.setEntity(new UrlEncodedFormEntity(urlParameters)); HttpResponse response = httpClient.execute(post); int status = response.getStatusLine().getStatusCode(); if(status < 400) { String content = EntityUtils.toString(response.getEntity()); GoToOrganizerG2T organizerVo = GoToJsonUtil.parseDirectLogin(content); return organizerVo; } else { error.setErrorCode(status); String responseString = EntityUtils.toString(response.getEntity()); EntityUtils.consumeQuietly(response.getEntity()); log.error("directLogin return " + status + ": " + responseString); } return null; } catch(Exception e) { log.error("", e); return null; } } @Override public List<GoToOrganizer> getOrganizers() { return organizerDao.getOrganizers(); } @Override public List<GoToOrganizer> getSystemOrganizers() { return organizerDao.getOrganizers(); } }