001package org.deepamehta.plugins.moodle; 002 003import de.deepamehta.core.Association; 004import de.deepamehta.core.DeepaMehtaObject; 005import de.deepamehta.core.RelatedTopic; 006import de.deepamehta.core.Topic; 007import de.deepamehta.core.model.*; 008import de.deepamehta.core.osgi.PluginActivator; 009import de.deepamehta.core.service.Inject; 010import de.deepamehta.core.service.ResultList; 011import de.deepamehta.core.service.Transactional; 012import de.deepamehta.core.storage.spi.DeepaMehtaTransaction; 013import de.deepamehta.core.util.JavaUtils; 014import de.deepamehta.plugins.accesscontrol.event.PostLoginUserListener; 015import de.deepamehta.plugins.accesscontrol.model.ACLEntry; 016import de.deepamehta.plugins.accesscontrol.model.AccessControlList; 017import de.deepamehta.plugins.accesscontrol.model.Operation; 018import de.deepamehta.plugins.accesscontrol.model.UserRole; 019import de.deepamehta.plugins.accesscontrol.service.AccessControlService; 020import java.io.*; 021import java.net.HttpURLConnection; 022import java.net.MalformedURLException; 023import java.net.URL; 024import java.security.KeyManagementException; 025import java.security.KeyStore; 026import java.security.KeyStoreException; 027import java.security.NoSuchAlgorithmException; 028import java.security.cert.CertificateException; 029import java.util.Date; 030import java.util.List; 031import java.util.Properties; 032import java.util.logging.Logger; 033import javax.net.ssl.HttpsURLConnection; 034import javax.net.ssl.SSLContext; 035import javax.net.ssl.TrustManagerFactory; 036import javax.ws.rs.*; 037import javax.ws.rs.core.MediaType; 038import org.codehaus.jettison.json.JSONArray; 039import org.codehaus.jettison.json.JSONException; 040import org.codehaus.jettison.json.JSONObject; 041 042 043/** 044 * 045 * A webservice-client enabling collaborative mapping on course-materials (and more) for any moodle 046 * (2.4+) course. A very simple plugin to connect users of DeepaMehta 4 with a moodle installation. 047 * 048 * @author Malte Reißig (<malte@mikromedia.de>) 049 * @website https://github.com/mukil/mapping-moodle 050 * @version 1.2.2-SNAPSHOT 051 * 052 */ 053 054@Path("/moodle") 055@Consumes(MediaType.APPLICATION_JSON) 056@Produces(MediaType.APPLICATION_JSON) 057public class MoodleServiceClient extends PluginActivator implements PostLoginUserListener { 058 059 // --- Mapping Moodle Plugin Type URIs 060 061 public static final String WS_MOODLE_NAME = "Moodle"; 062 public static final String WS_MOODLE_URI = "org.deepamehta.workspaces.moodle"; 063 public static final String MOODLE_PARTICIPANT_EDGE = "org.deepamehta.moodle.course_participant"; 064 065 public static final String MOODLE_COURSE_URI = "org.deepamehta.moodle.course"; 066 public static final String MOODLE_COURSE_NAME_URI = "org.deepamehta.moodle.course_name"; 067 public static final String MOODLE_COURSE_SHORT_NAME_URI = "org.deepamehta.moodle.course_short_name"; 068 069 public static final String MOODLE_SECTION_URI = "org.deepamehta.moodle.section"; 070 public static final String MOODLE_SECTION_NAME_URI = "org.deepamehta.moodle.section_name"; 071 public static final String MOODLE_SECTION_SUMMARY_URI = "org.deepamehta.moodle.section_summary"; 072 public static final String MOODLE_SECTION_ORDINAL_NR = "org.deepamehta.moodle.section_ordinal_nr"; 073 074 public static final String MOODLE_ITEM_URI = "org.deepamehta.moodle.item"; 075 public static final String MOODLE_ITEM_NAME_URI = "org.deepamehta.moodle.item_name"; 076 public static final String MOODLE_ITEM_ICON_URI = "org.deepamehta.moodle.item_icon"; 077 public static final String MOODLE_ITEM_REMOTE_URL_URI = "org.deepamehta.moodle.item_url"; 078 public static final String MOODLE_ITEM_MEDIA_TYPE_URI = "org.deepamehta.moodle.item_media_type"; 079 public static final String MOODLE_ITEM_DESC_URI = "org.deepamehta.moodle.item_description"; 080 public static final String MOODLE_ITEM_HREF_URI = "org.deepamehta.moodle.item_href"; 081 public static final String MOODLE_ITEM_TYPE_URI = "org.deepamehta.moodle.item_type"; 082 public static final String MOODLE_ITEM_MODIFIED_URI = "org.deepamehta.moodle.item_modified"; 083 public static final String MOODLE_ITEM_CREATED_URI = "org.deepamehta.moodle.item_created"; 084 public static final String MOODLE_ITEM_AUTHOR_URI = "org.deepamehta.moodle.item_author"; 085 public static final String MOODLE_ITEM_LICENSE_URI = "org.deepamehta.moodle.item_license"; 086 public static final String MOODLE_ITEM_SIZE_URI = "org.deepamehta.moodle.item_size"; 087 088 // --- URIs used for imported Moodle Topics 089 090 public static final String ISIS_COURSE_URI_PREFIX = "de.tu-berlin.course."; 091 public static final String ISIS_SECTION_URI_PREFIX = "de.tu-berlin.section."; 092 public static final String ISIS_ITEM_URI_PREFIX = "de.tu-berlin.item."; 093 094 // --- Mapping Moodle HTTP Client Configuration related URIs 095 096 private final String WEBSERVICE_CLIENT_OPTION_OPTION = "org.deepamehta.moodle.web_service_client_option"; 097 private final String CLIENT_OPTION_ENDPOINT_URL_URI = "org.deepamehta.moodle.web_service_endpoint_url"; 098 private final String CLIENT_OPTION_USE_HTTPS = "org.deepamehta.moodle.use_https"; 099 private final String CLIENT_OPTION_JAVA_KEY_STORE_PATH = "org.deepamehta.moodle.jks_path"; 100 private final String CLIENT_OPTION_JAVA_KEY_STORE_PASS = "org.deepamehta.moodle.jks_pass"; 101 // The Username eligible to 102 // a) edit the moodle service-settings (endpoint) and all other system-created items, as well is allowed to 103 // b) set "Tags" on each "Moodle Course" (which is necessary) before they can be synced 104 private final String USERNAME_OF_SETTINGS_ADMINISTRATOR = "admin"; 105 private final String MOODLE_SECURITY_KEY_URI = "org.deepamehta.moodle.security_key"; 106 private final String MOODLE_USER_ID_URI = "org.deepamehta.moodle.user_id"; 107 private final String MOODLE_SERVICE_NAME = "eduzen_web_service"; 108 private final String MOODLE_SERVICE_FORMAT = "moodlewsrestformat=json"; 109 private final int MOODLE_SECURITY_KEY_LENGTH = 32; 110 111 // --- Deepamehta 4 Core URIs 112 113 private final String URI_URI = "uri"; 114 private final String DEFAULT_ROLE_TYPE_URI = "dm4.core.default"; 115 private final String CHILD_ROLE_TYPE_URI = "dm4.core.child"; 116 private final String PARENT_ROLE_TYPE_URI = "dm4.core.parent"; 117 private final String AGGREGATION_TYPE_URI = "dm4.core.aggregation"; 118 private final String COMPOSITION_TYPE_URI = "dm4.core.composition"; 119 private final String USER_ACCOUNT_TYPE_URI = "dm4.accesscontrol.user_account"; 120 private final String USER_NAME_TYPE_URI = "dm4.accesscontrol.username"; 121 private final String CHILD_URI = "dm4.core.child"; 122 private final String PARENT_URI = "dm4.core.parent"; 123 private final String TAG_URI = "dm4.tags.tag"; 124 private final String REVIEW_SCORE_URI = "org.deepamehta.reviews.score"; 125 126 private static Logger logger = Logger.getLogger(MoodleServiceClient.class.getName()); 127 128 // ### Consume NotifcationService 129 @Inject 130 private AccessControlService aclService; 131 132 133 134 // -- 135 // --- Plugin Hook implementations 136 // -- 137 138 @Override 139 public void init() { 140 Properties systemProps = System.getProperties(); 141 logger.info("MoodleServiceClient: JRE Keystore (Truststore) is set to " 142 + "\"" + systemProps.getProperty("javax.net.ssl.trustStore") +"\""); 143 } 144 145 @Override 146 public void postInstall() { 147 if (aclService != null) { 148 DeepaMehtaTransaction tx = dms.beginTx(); 149 try { 150 // ### remember: topics introduced via migration1 have no ACLs set. 151 // 1) correct ACL of migration1 workspace topic 152 Topic moodleWs = dms.getTopic(URI_URI, new SimpleValue(WS_MOODLE_URI)); 153 setDefaultMoodleAdminACLEntries(moodleWs); 154 // 2) Correct ACL of topics in migration4 155 ResultList<RelatedTopic> client_options = dms.getTopics(WEBSERVICE_CLIENT_OPTION_OPTION, 0); 156 for (RelatedTopic client_option : client_options) { 157 setDefaultMoodleAdminACLEntries(client_option); 158 } 159 tx.success(); 160 } finally { 161 tx.finish(); 162 } 163 } 164 } 165 166 @Override 167 public void postLoginUser(String username) { 168 startMoodleSynchronization(username); 169 } 170 171 172 173 // -- 174 // --- Moodle Plugin REST API Endpoints 175 // -- 176 177 @GET 178 @Path("/synchronize/{username}") 179 @Produces(MediaType.TEXT_PLAIN) 180 @Transactional 181 public String startMoodleSynchronization(@PathParam("username") String username) { 182 final Topic user = checkAuthorization(); 183 // 1) Twofold sanity check 184 if (!user.getSimpleValue().toString().equals(username)) { 185 logger.info("MoodleServiceClient sanity check failed " 186 + username + " != " + user.getSimpleValue().toString()); 187 throw new RuntimeException(); 188 } 189 // 2) Start Moodle-Sync for this username 190 startSynchronizationThreadFor(user); 191 return "OK"; 192 193 } 194 195 /** 196 * Delivers the moodle security key for the logged in user to the requesting client. 197 * This is needed to load course materials (esp. files) directly into the browser where thens, 198 * e.g. in Firefox <object>-embeds for PDF materials call a plugin to (e.g. PDF.js) to render the file 199 * directly in the page panel next to the topicmap in which the course material was selected. 200 */ 201 @GET 202 @Path("/key") 203 @Produces(MediaType.TEXT_PLAIN) 204 public String getMoodleSecurityKey() { 205 Topic userAccount = checkAuthorization(); 206 if (userAccount.hasProperty(MOODLE_SECURITY_KEY_URI)) { 207 String token = (String) userAccount.getProperty(MOODLE_SECURITY_KEY_URI); 208 return token; 209 } 210 return null; 211 } 212 213 /** 214 * Relates the moodle-security-key to our currently logged-in user-account. 215 * Keeps the (external) user id (from moodle) as a property, which is needed 216 * for subsequent requests on behalf of the user. 217 * 218 * @param id topicId of userAccount 219 * @param input JSON payload containing { "moodle_key": yourkey } 220 **/ 221 @POST 222 @Path("/key/{id}") 223 @Transactional 224 public String setMoodleSecurityKey(@PathParam("id") int id, final String input ) { 225 try { 226 // 1) Store security key for this user 227 Topic userAccount = checkAuthorization(); 228 if (userAccount.getId() != id) throw new WebApplicationException(new RuntimeException("Not allowed"), 401); 229 Topic user = dms.getTopic(id).loadChildTopics(); 230 JSONObject payload = new JSONObject(input); 231 String moodle_key = payload.getString("moodle_key"); 232 // 2) prevent setting of keys which do not look like a moodle security key 233 if (moodle_key.equals("") || moodle_key.length() != MOODLE_SECURITY_KEY_LENGTH) { 234 throw new RuntimeException("Sorry but that does not look like a \"Moodle security key.\""); 235 } 236 user.setProperty(MOODLE_SECURITY_KEY_URI, moodle_key, false); // addToIndex=false **/ 237 // 3) Fetch user_id from moodle installation (to be able to start querying the service) 238 fetchAndSetMoodleUserId(userAccount); 239 return "{ \"result\": \"OK\"}"; 240 } catch (JSONException ex) { 241 logger.warning("We could not set your moodle user id because of an error while parsing the response " 242 + ex.getMessage()); 243 return "{ \"result\": \"FAIL\"}"; 244 } catch (Exception wex) { 245 logger.warning("We could not set your moodle user id " + wex.getMessage() 246 + " (" + wex.getCause().toString() + ")"); 247 return "{ \"result\": \"FAIL\"}"; 248 } 249 } 250 251 252 // -- 253 // --- Private helper methods / Plugin Implementation 254 // -- 255 256 private void startSynchronizationThreadFor(Topic user) { 257 258 final String user_name = user.getChildTopics().getString(USER_NAME_TYPE_URI); 259 final Topic local_user = user; 260 261 new Thread() { 262 263 @Override 264 public void run() { 265 266 logger.info("Started MoodleServiceClient-Synchronization Thread due to a _login-Event .. "); 267 // 2) Fetch Moodle Security Key for newly logged in user 268 String key = getMoodleSecurityKeyWithoutAuthCheck(local_user); 269 if (key != null && key.length() == 32) { // 2) Check if it looks like a legit one for MOODLE 270 // todo: check if we have a MoodleUserID for this user .. 271 logger.fine("MoodleServiceclient found a security key looking legit for \"" + user_name + "\""); 272 } else { 273 throw new RuntimeException("This user has no \"Moodle Security Key\" set no SYNC.. "); 274 } 275 // 3) Ask for potentially new \"Moodle Courses\" .. 276 logger.fine("MoodleServiceClient checking for news in all \"Moodle Courses\" of \""+user_name+"\""); 277 // and create them if necessary / yet unknown to our system/installation 278 fetchMoodleCourses(local_user, getMoodleUserId(local_user), key); 279 // 4) Check for new \"Moodle Items\" in each tagged \"Moodle Course\" our user is "participating" 280 ResultList<RelatedTopic> courses = getMoodleCourseTopicsByUser(local_user); 281 if (courses != null) { 282 for (RelatedTopic course : courses) { 283 // 5) excluse Moodle Courses with no "Moodle Course Hashtag" set from synchronziations 284 course.loadChildTopics(TAG_URI); 285 if (course.getChildTopics().has(TAG_URI) && 286 course.getChildTopics().getTopics(TAG_URI).size() > 0) { 287 List<Topic> courseHashtags = course.getChildTopics().getTopics(TAG_URI); 288 logger.info("MoodleServiceClient SYNC course \"" + course.getSimpleValue() +"\" " 289 + "under " +courseHashtags.size() + " hashtags:"); 290 for (Topic hashtag : courseHashtags) { 291 logger.info("\t#" + hashtag.getSimpleValue()); 292 } 293 fetchMoodleCourseItems(course.getId(), key, courseHashtags); 294 } else { 295 logger.info("MoodleServiceClient waiting with SYNC cause of missing #Hashtag (on \"" 296 + course.getSimpleValue() + "\")"); 297 } 298 } 299 } else { 300 logger.warning("Moodle Courses related to this user are NULL"); 301 } 302 } 303 }.start(); 304 } 305 306 /** Fetches and relates the internal moodle-user-id to our currently logged-in user-account. **/ 307 private void fetchAndSetMoodleUserId(Topic userAccount) throws WebApplicationException { 308 String token = getMoodleSecurityKeyWithoutAuthCheck(userAccount); 309 if (token == null) throw new WebApplicationException(new RuntimeException("User has no security key."), 500); 310 String parameter = "serviceshortnames[0]=" + MOODLE_SERVICE_NAME; 311 String data = ""; 312 try { 313 data = callMoodle(token, "core_webservice_get_site_info", parameter); 314 JSONObject response = new JSONObject(data.toString()); 315 long userId = response.getLong("userid"); 316 setMoodleUserId(userAccount, userId); 317 } catch (JSONException ex) { 318 logger.warning("Moodle JSONException " + ex.getMessage().toString()); 319 try { 320 JSONObject exception = new JSONObject(data.toString()); 321 logger.warning("MoodleResponseException is \"" + exception.getString("message") +"\""); 322 } catch (JSONException ex1) { 323 throw new RuntimeException(ex1); 324 } 325 } catch (MoodleConnectionException mc) { 326 logger.warning("MoodleConnectionException \"" + mc.message + "\" (" + mc.status + ")"); 327 throw new WebApplicationException(mc, mc.status); 328 } 329 } 330 331 private void fetchMoodleCourses(Topic userAccount, long moodleUserId, String token) { 332 String parameter = "userid=" + moodleUserId; 333 String data = ""; 334 DeepaMehtaTransaction tx = dms.beginTx(); 335 try { 336 data = callMoodle(token, "core_enrol_get_users_courses", parameter); 337 if (data.indexOf("webservice_access_exception") != -1) { 338 logger.warning("Looks like external service (webservice feature) is not \"Enabled\" " 339 + "or the called function is not part of the \"External Service\" defintion " 340 + "on the configured Moodle installation."); 341 throw new WebApplicationException(new MoodleConnectionException(data, 404), 404); 342 } 343 // 344 JSONArray response = new JSONArray(data.toString()); 345 for (int i = 0; i < response.length(); i++) { 346 JSONObject course = response.getJSONObject(i); 347 Topic courseTopic = getMoodleCourseTopic(course.getLong("id")); 348 if (courseTopic == null) { 349 // 1) Create new item 350 courseTopic = createMoodleCourseTopic(course); 351 if (courseTopic != null) { 352 // 2) Fix ACLEntries (caused by missing request-scope in Thread-local) 353 setDefaultMoodleAdminACLEntries(courseTopic); // just "admin" can edit course-items 354 // sendClientNotification("New Moodle Course", courseTopic); 355 } else { 356 logger.info("OMITTING HIDDEN MoodleCourse \"" + course.getString("shortname") + "\""); 357 } 358 } else { 359 // 2) Update existing item 360 updateMoodleCourseTopic(courseTopic, course); 361 } 362 // 3) Relate to course item 363 if (!hasParticipantEdge(courseTopic, userAccount)) { 364 createParticipantEdge(courseTopic, userAccount); 365 } 366 } 367 tx.success(); 368 } catch (JSONException ex) { 369 tx.failure(); 370 logger.warning("# ROLLBACK!"); 371 try { 372 JSONObject response = new JSONObject(data.toString()); 373 String exception = response.getString("exception"); 374 logger.warning("Moodle Response Exception Message => " + exception); 375 throw new WebApplicationException(new MoodleConnectionException(exception, 500), 500); 376 } catch (JSONException ex1) { 377 throw new RuntimeException(ex1); 378 } 379 } catch (MoodleConnectionException mc) { 380 logger.severe("Moodle Connection Exception => " + mc.message + " Status: " + mc.status); 381 throw new WebApplicationException(new Throwable(mc.message), mc.status); 382 } finally { 383 tx.finish(); 384 } 385 } 386 387 private void fetchMoodleCourseItems(long topicId, String token, List<Topic> hashtags) { 388 long courseId = -1; 389 Topic courseTopic = dms.getTopic(topicId); 390 courseId = Long.parseLong(courseTopic.getUri().replaceAll(ISIS_COURSE_URI_PREFIX, "")); 391 String parameter = "courseid=" + courseId; 392 String data = ""; 393 DeepaMehtaTransaction tx = dms.beginTx(); 394 try { 395 data = callMoodle(token, "core_course_get_contents", parameter); 396 JSONArray response = new JSONArray(data.toString()); 397 for (int i = 0; i < response.length(); i++) { 398 JSONObject section = response.getJSONObject(i); 399 Topic sectionTopic = getMoodleSectionTopic(section.getLong("id")); 400 // 1) Create new \"Moodle Section\" 401 if (sectionTopic == null) { 402 sectionTopic = createMoodleSectionTopic(section, i); 403 if (sectionTopic != null) { 404 // Fix Section-ACL-Entries so that "admin" can edit them 405 setDefaultMoodleAdminACLEntries(sectionTopic); 406 // sendClientNotification("New Moodle Section in \"" + 407 // courseTopic.getSimpleValue().toString() + "\"", sectionTopic); 408 } 409 // 2) Update existing \"Moodle section\" 410 } else { 411 updateMoodleSectionTopic(sectionTopic, section); 412 } 413 // 3) Create or Update all \"Moodle Items\" 414 if (sectionTopic != null) { // (if section is not null/ hidden) 415 JSONArray modules = section.getJSONArray("modules"); 416 for (int k = 0; k < modules.length(); k++) { 417 JSONObject item = modules.getJSONObject(k); 418 Topic itemTopic = getMoodleCourseItemTopic(item.getLong("id")); 419 if (itemTopic == null) { 420 itemTopic = createMoodleCourseItemTopic(item, hashtags); 421 if (itemTopic != null) { 422 // Fix ACL so that all "Moodle"-WS Members can edit these items 423 setDefaultMoodleGroupACLEntries(itemTopic); 424 // Association creator_edge = assignDefaultAuthorship(itemTopic); 425 // if (creator_edge != null) setDefaultMoodleGroupACLEntries(creator_edge); 426 } 427 } else { 428 updateMoodleCourseItemTopic(itemTopic, item); 429 } 430 if (!hasAggregatingSectionParentEdge(itemTopic, sectionTopic)) { 431 createAggregatingSectionEdge(sectionTopic, itemTopic); 432 } 433 } 434 } 435 // 4) Assign new \"Moodle section\" to course (if section is not null/hidden) 436 if (!hasAggregatingCourseParentEdge(sectionTopic, courseTopic)) { 437 createAggregatingCourseEdge(courseTopic, sectionTopic); 438 } 439 } 440 logger.info("MoodleServiceClient finished loading materials for course \"" 441 + courseTopic.getSimpleValue()+"\""); 442 tx.success(); 443 } catch (JSONException ex) { 444 tx.failure(); 445 logger.warning("# ROLLBACK!"); 446 logger.warning("Could not get contents for \"Moodle Course\"-Topic: " 447 + ex.getMessage() + "(" + ex.getClass() + ")"); 448 try { 449 JSONObject exception = new JSONObject(data.toString()); 450 String message = exception.getString("message"); 451 throw new WebApplicationException(new MoodleConnectionException(message, 500), 500); 452 } catch (JSONException ex1) { 453 throw new RuntimeException(ex1); 454 } 455 } catch (MoodleConnectionException mc) { 456 throw new WebApplicationException(mc, mc.status); 457 } finally { 458 tx.finish(); 459 } 460 } 461 462 private void createParticipantEdge(Topic courseTopic, Topic userAccount) { 463 AssociationModel participantEdge = new AssociationModel(MOODLE_PARTICIPANT_EDGE, 464 new TopicRoleModel(courseTopic.getId(), DEFAULT_ROLE_TYPE_URI), 465 new TopicRoleModel(userAccount.getId(), DEFAULT_ROLE_TYPE_URI)); 466 Association edge = dms.createAssociation(participantEdge); 467 } 468 469 private boolean hasParticipantEdge (Topic course, Topic user) { 470 boolean value = false; 471 Topic userAccount = course.getRelatedTopic(MOODLE_PARTICIPANT_EDGE, DEFAULT_ROLE_TYPE_URI, 472 DEFAULT_ROLE_TYPE_URI, USER_ACCOUNT_TYPE_URI); 473 if (userAccount != null && user.getId() == userAccount.getId()) return value = true; 474 return value; 475 } 476 477 private void createAggregatingCourseEdge (Topic courseTopic, Topic sectionTopic) { 478 AssociationModel aggregationEdge = new AssociationModel(AGGREGATION_TYPE_URI, 479 new TopicRoleModel(courseTopic.getId(), PARENT_ROLE_TYPE_URI), 480 new TopicRoleModel(sectionTopic.getId(), CHILD_ROLE_TYPE_URI)); 481 dms.createAssociation(aggregationEdge); 482 } 483 484 private boolean hasAggregatingCourseParentEdge (Topic child, Topic parent) { 485 boolean value = false; 486 if (child == null) return true; 487 Topic topic = child.getRelatedTopic(AGGREGATION_TYPE_URI, CHILD_ROLE_TYPE_URI, 488 PARENT_ROLE_TYPE_URI, MOODLE_COURSE_URI); 489 if (topic != null && parent.getId() == topic.getId()) return value = true; 490 return value; 491 } 492 493 private void createAggregatingSectionEdge (Topic sectionTopic, Topic itemTopic) { 494 AssociationModel aggregationEdge = new AssociationModel(AGGREGATION_TYPE_URI, 495 new TopicRoleModel(sectionTopic.getId(), PARENT_ROLE_TYPE_URI), 496 new TopicRoleModel(itemTopic.getId(), CHILD_ROLE_TYPE_URI)); 497 dms.createAssociation(aggregationEdge); 498 } 499 500 private boolean hasAggregatingSectionParentEdge (Topic child, Topic parent) { 501 boolean value = false; 502 if (child == null) return true; 503 Topic topic = child.getRelatedTopic(AGGREGATION_TYPE_URI, CHILD_ROLE_TYPE_URI, 504 PARENT_ROLE_TYPE_URI, MOODLE_SECTION_URI); 505 if (topic != null && parent.getId() == topic.getId()) return value = true; 506 return value; 507 } 508 509 private Topic createMoodleCourseTopic(JSONObject object) { 510 Topic result = null; 511 try { 512 if (object.getInt("visible") == 0) return null; 513 long courseId = object.getLong("id"); 514 String shortName = object.getString("shortname"); 515 String fullName = object.getString("fullname"); 516 ChildTopicsModel model = new ChildTopicsModel(); 517 model.put(MOODLE_COURSE_NAME_URI, fullName); 518 model.put(MOODLE_COURSE_SHORT_NAME_URI, shortName); 519 TopicModel course = new TopicModel(ISIS_COURSE_URI_PREFIX + courseId, MOODLE_COURSE_URI, model); 520 course.setUri(ISIS_COURSE_URI_PREFIX + courseId); 521 result = dms.createTopic(course); 522 assignToMoodleWorkspace(result); 523 } catch (JSONException ex) { 524 logger.warning("Could not create \"Moodle Course\"-Topic: " 525 + ex.getMessage() + "(" + ex.getClass() + ")"); 526 } 527 return result; 528 } 529 530 private Topic updateMoodleCourseTopic(Topic course, JSONObject object) { 531 try { 532 boolean update_this = false; 533 String new_name = object.getString("shortname"); 534 String new_fullname = object.getString("fullname"); 535 if (!course.getChildTopics().getString(MOODLE_COURSE_SHORT_NAME_URI).equals(new_name) || 536 !course.getChildTopics().getString(MOODLE_COURSE_NAME_URI).equals(new_fullname)) { 537 update_this = true; 538 } 539 // Update (if there were changes) 540 if (update_this) { 541 ChildTopicsModel model = new ChildTopicsModel(); 542 model.put(MOODLE_COURSE_NAME_URI, new_name); 543 model.put(MOODLE_COURSE_SHORT_NAME_URI, new_fullname); 544 dms.updateTopic(new TopicModel(course.getId(), model)); 545 // todo: add to usage report 546 } 547 return course; 548 } catch (JSONException ex) { 549 logger.warning("Could not update \"Moodle Course\"-Topic: " 550 + ex.getMessage() + "(" + ex.getClass() + ")"); 551 } 552 return null; 553 } 554 555 private Topic getMoodleCourseTopic(long courseId) { 556 return dms.getTopic("uri", new SimpleValue(ISIS_COURSE_URI_PREFIX + courseId)); 557 } 558 559 private ResultList<RelatedTopic> getMoodleCourseTopicsByUser(Topic user) { 560 return user.getRelatedTopics(MOODLE_PARTICIPANT_EDGE, DEFAULT_ROLE_TYPE_URI, 561 DEFAULT_ROLE_TYPE_URI, MOODLE_COURSE_URI, 0); 562 } 563 564 private Topic createMoodleSectionTopic(JSONObject object, int nr) { 565 try { 566 if (object.getInt("visible") == 0) return null; // item is hidden, do not create it 567 long sectionId = object.getLong("id"); 568 String name = object.getString("name"); 569 String summary = object.getString("summary"); 570 ChildTopicsModel model = new ChildTopicsModel(); 571 model.put(MOODLE_SECTION_NAME_URI, name); 572 model.put(MOODLE_SECTION_SUMMARY_URI, summary); 573 model.put(MOODLE_SECTION_ORDINAL_NR, nr); 574 TopicModel section = new TopicModel(ISIS_SECTION_URI_PREFIX + sectionId, MOODLE_SECTION_URI, model); 575 // OWNER AND CREATOR will be the (logged in user) who triggered this method IN ANY CASE 576 Topic result = dms.createTopic(section); 577 setDefaultMoodleAdminACLEntries(result); // just "admin" can edit these 578 assignToMoodleWorkspace(result); 579 return result; 580 } catch (JSONException ex) { 581 logger.warning("Could not create \"Moodle Section\"-Topic: " 582 + ex.getMessage() + "(" + ex.getClass() + ")"); 583 } 584 return null; 585 } 586 587 private Topic updateMoodleSectionTopic(Topic section, JSONObject object) { 588 try { 589 boolean update_this = false; 590 String new_name = object.getString("name"); 591 String new_summary = object.getString("summary"); 592 // Custom comparison of values 593 if (!section.getChildTopics().getString(MOODLE_SECTION_NAME_URI).equals(new_name) || 594 !section.getChildTopics().getString(MOODLE_SECTION_SUMMARY_URI).equals(new_summary)) { 595 update_this = true; 596 } 597 // Update (if there were changes) 598 if (update_this) { 599 ChildTopicsModel model = new ChildTopicsModel(); 600 model.put(MOODLE_SECTION_NAME_URI, new_name); 601 model.put(MOODLE_SECTION_SUMMARY_URI, new_summary); 602 section.setChildTopics(model); 603 } 604 } catch (JSONException ex) { 605 logger.warning("Could not update \"Moodle Section\"-Topic: " 606 + ex.getMessage() + "(" + ex.getClass() + ")"); 607 } 608 return null; 609 } 610 611 private Topic getMoodleSectionTopic(long sectionId) { 612 return dms.getTopic("uri", new SimpleValue(ISIS_SECTION_URI_PREFIX + sectionId)); 613 } 614 615 private Topic createMoodleCourseItemTopic(JSONObject object, List<Topic> hashtags) { 616 try { 617 // 0) Check if given moodle item is actually "hidden" and if not remember moodle-id 618 if (object.getInt("visible") == 0) return null; // item is hidden, do not create it 619 long itemId = object.getLong("id"); 620 // 1) Parse some generic information for this item 621 ChildTopicsModel model = parseGenericsToItemModel(object); 622 // 2) and if it's a material (typeof "resource" or "url") process its list of "contents" 623 JSONArray contents = null; 624 if (object.has("contents")) { 625 contents = object.getJSONArray("contents"); 626 for (int i = 0; i < contents.length(); i++) { // (actually never in use, but possible) 627 JSONObject resource = contents.getJSONObject(i); 628 parseResourceToItemModel(model, resource); 629 // ..) equip moodle item with timestamp of or resource (e.g. files) 630 parseTimestampsToItemModel(model, resource); 631 } 632 } else { 633 // 3) equip any other item with default timestamps (or the timestamps of the object, not resource) 634 parseTimestampsToItemModel(model, object); 635 } 636 // 4) equip moodle item with every hashtag set on the course 637 for (Topic hashtag : hashtags) { 638 model.addRef(TAG_URI, hashtag.getId()); 639 } 640 // 5) construct our internal uri for any moodl item 641 TopicModel item = new TopicModel(ISIS_ITEM_URI_PREFIX + itemId, MOODLE_ITEM_URI, model); 642 Topic result = dms.createTopic(item); 643 // 6) fix workspace assignment for shared editing (tagging) of moodle items via webclient 644 assignToMoodleWorkspace(result); 645 // sendTopicNotification("New Moodle Item", result); 646 return result; 647 } catch (JSONException ex) { 648 logger.warning("Could not create \"Moodle Item\"-Topic: " 649 + ex.getMessage() + "(" + ex.getClass() + ")"); 650 } 651 return null; 652 } 653 654 private Topic updateMoodleCourseItemTopic(Topic item, JSONObject object) { 655 DeepaMehtaTransaction tx = dms.beginTx(); 656 try { 657 // 0) parse new generic infos to new model 658 ChildTopicsModel model = parseGenericsToItemModel(object); 659 // 1) parse infos specific to resources to model 660 JSONArray contents = null; 661 long last_modified_in_moodle = 0; 662 if (object.has("contents")) { 663 contents = object.getJSONArray("contents"); 664 for (int i = 0; i < contents.length(); i++) { // (no observation that this is >1, but possible) 665 // if list of contents in a module is >1, currently the last one succeeds 666 JSONObject resource = contents.getJSONObject(i); 667 parseResourceToItemModel(model, resource); 668 parseTimestampsToItemModel(model, resource); 669 } 670 } else { 671 // 2) equip ay other item with the new (moodle or our default) timestamps first 672 parseTimestampsToItemModel(model, object); 673 } 674 // 2) start to perform update checks on contents of this (new) item 675 boolean update_this = false; 676 if (!item.getSimpleValue().toString().equals(model.getString(MOODLE_ITEM_NAME_URI))) { // by item-name 677 // log.info("MoodleServiceClient: The name of the item has changed: ITEM is TO_BE_UPDATED"); 678 update_this = true; 679 } else if (item.getChildTopics().getString(MOODLE_ITEM_TYPE_URI).equals("url")) { // by url-value 680 // on "url"-items we perform a comparison by-value (cause timestamps are always missing) 681 if (!item.getChildTopics().getString(MOODLE_ITEM_REMOTE_URL_URI) 682 .equals(model.getString(MOODLE_ITEM_REMOTE_URL_URI))) { 683 // log.info("MoodleServiceClient: The url-value changed: ITEM is TO_BE_UPDATED"); 684 update_this = true; 685 } 686 } else if (item.getChildTopics().has(MOODLE_ITEM_MODIFIED_URI)) { 687 // note: sometimes int sometimes long (webclient!) cause of neo4j and moodles timestamps/1000 688 // check the last_modification timestamp from on our topic (in our current DB) 689 long existing_timestamp = item.getChildTopics().getLong(MOODLE_ITEM_MODIFIED_URI); 690 if (existing_timestamp < last_modified_in_moodle) { // lets write new contents to our \"Moodle Item\" 691 logger.fine("MoodleServiceClient: The remote Last-Modified-Timestamp indicates: " 692 + "ITEM is TO_BE_UPDATED"); 693 update_this = true; 694 } 695 } 696 if (update_this) { 697 // 3) write update 698 dms.updateTopic(new TopicModel(item.getId(), model)); 699 // sendClientNotification("Changed Moodle Item in \"" 700 // + courseTopic.getSimpleValue().toString() + "\"", item); 701 } 702 tx.success(); 703 return item; 704 } catch (JSONException ex) { 705 tx.failure(); 706 logger.warning("# ROLLBACK!"); 707 logger.warning("Could not update \"Moodle Item\"-Topic: " 708 + ex.getMessage() + "(" + ex.getClass() + ")"); 709 } finally { 710 tx.finish(); 711 } 712 return null; 713 } 714 715 private Topic getMoodleCourseItemTopic(long itemId) { 716 return dms.getTopic("uri", new SimpleValue(ISIS_ITEM_URI_PREFIX + itemId)); 717 } 718 719 private void setMoodleUserId(Topic userAccount, long moodleUserId) { 720 DeepaMehtaTransaction tx = dms.beginTx(); 721 userAccount.setProperty(MOODLE_USER_ID_URI, "" + moodleUserId + "", false); 722 tx.success(); 723 tx.finish(); 724 } 725 726 private long getMoodleUserId(Topic userAccount) { 727 if (userAccount.hasProperty(MOODLE_USER_ID_URI)) { 728 String id = (String) userAccount.getProperty(MOODLE_USER_ID_URI); 729 long moodle_user_id = Long.parseLong(id); 730 return moodle_user_id; 731 } 732 return -1; 733 } 734 735 736 737 // -- 738 // --- Moodle JSON Response parser methods 739 // -- 740 741 private ChildTopicsModel parseGenericsToItemModel(JSONObject object) throws JSONException { 742 // 0) parse name, icon and type 743 String name = object.getString("name"); 744 String iconPath = object.getString("modicon"); 745 String type = object.getString("modname"); 746 String description = "", href = ""; 747 // 1) check for an optional description (e.g. "choice", or "label") 748 if (object.has("description")) { 749 description = object.getString("description"); 750 } 751 // 2) check for a moodle item url so we can link directly to the content (into the moodle installation) 752 if (object.has("url")) { 753 href = object.getString("url"); 754 } 755 // 3) Initialize the values of each moodle item 756 ChildTopicsModel model = new ChildTopicsModel(); 757 model.put(MOODLE_ITEM_NAME_URI, name); 758 model.put(MOODLE_ITEM_ICON_URI, iconPath); 759 model.put(MOODLE_ITEM_DESC_URI, description); 760 model.put(MOODLE_ITEM_HREF_URI, href); // exists always (if item is not of type "label") 761 model.put(MOODLE_ITEM_TYPE_URI, type); 762 // ..) with a neutral review-score 763 model.put(REVIEW_SCORE_URI, 0); 764 return model; 765 } 766 767 private ChildTopicsModel parseResourceToItemModel(ChildTopicsModel model, JSONObject resource) 768 throws JSONException { 769 String resourceType = resource.getString("type"); 770 String fileUrl = "", fileName = "", author = "", license = ""; 771 // 1) Fill up either web resource or file-type 772 if (resourceType.equals("url")) { // Moodle Resource is an URL 773 fileUrl = resource.getString("fileurl"); 774 // 1.1) parse youtube and replace /watch?v=id with /embed/id in the url 775 if (fileUrl.contains("youtube") || fileUrl.contains("youtu.be")) { 776 // turn youtube-share url into /embed/-url 777 fileUrl = parseYoutubeEmbedUrl(fileUrl); 778 } 779 model.put(MOODLE_ITEM_REMOTE_URL_URI, fileUrl); 780 // add it to the (to be created) Moodle Item 781 model.put(MOODLE_ITEM_TYPE_URI, resourceType); 782 // alternatively: model.put(MOODLE_ITEM_HREF_URI, fileurl); 783 } else if (resourceType.equals("file")) { // Moodle Resource is an URL 784 // pages _and_ documents are of type file 785 fileName = resource.getString("filename"); 786 fileUrl = resource.getString("fileurl"); 787 if (!resource.isNull("license")) { 788 license = resource.getString("license"); 789 model.put(MOODLE_ITEM_LICENSE_URI, license); 790 } 791 if (!resource.isNull("author")) { 792 author = resource.getString("author"); 793 model.put(MOODLE_ITEM_AUTHOR_URI, author); 794 } 795 long fileSize = resource.getLong("filesize"); 796 model.put(MOODLE_ITEM_NAME_URI, fileName); 797 model.put(MOODLE_ITEM_REMOTE_URL_URI, fileUrl); 798 model.put(MOODLE_ITEM_SIZE_URI, fileSize); 799 String file_type = JavaUtils.getFileType(fileName); 800 // 1.1) Try to determine file media_type with the help of DM-Utils 801 if (file_type == null) file_type = "Unknown"; // Maybe null (e.g. for .ODT-Documents) 802 model.put(MOODLE_ITEM_MEDIA_TYPE_URI, file_type); 803 model.put(MOODLE_ITEM_TYPE_URI, resourceType); 804 } 805 // The new model will just be written to topic/db if our custom update-check (see caller) succeeds 806 return model; 807 } 808 809 private ChildTopicsModel parseTimestampsToItemModel(ChildTopicsModel model, JSONObject item) 810 throws JSONException { 811 long last_modified = 0, time_created = 0; 812 // 1) Check for timestamp "Last Modified" 813 if (item.has("timemodified") && !item.isNull("timemodified")) { 814 last_modified = item.getLong("timemodified") * 1000; // addding three zeros 815 } else { // e.g. contents of type "url" NEVER have any timestamp set, setting it to NOW 816 last_modified = new Date().getTime(); 817 } 818 // 2) Check for timestamp "Created" 819 if (item.has("timecreated") && !item.isNull("timecreated")) { 820 time_created = item.getLong("timecreated") * 1000; // addding three zeros; 821 } else { // e.g. contents of type "url" NEVER have any timestamp set, setting it to NOW 822 time_created = new Date().getTime(); 823 } 824 model.put(MOODLE_ITEM_MODIFIED_URI, last_modified); 825 model.put(MOODLE_ITEM_CREATED_URI, time_created); 826 return model; 827 } 828 829 private String parseYoutubeEmbedUrl(String fileUrl) { 830 String new_url = fileUrl; 831 // 0) Skip transformation if url already contains the "/embed/"-part 832 if (fileUrl.contains("/embed")) return new_url; 833 // 1) Transform URL Scheme: http://www.youtube.com/watch?v=A9b9bxUyK0o 834 if (fileUrl.contains("/watch")) { 835 String[] parts = fileUrl.split("v="); 836 // Strip of everything behind & e.g.: &feature=youtube_gdata 837 String new_url_part = ""; 838 int indexOfParameters = parts[1].indexOf("&"); 839 if (parts[1] != null && indexOfParameters != -1) { 840 new_url_part = parts[1].substring(0, indexOfParameters); 841 new_url = "//youtube.com/embed/" + new_url_part; 842 } else if (parts[1] != null) { 843 new_url = "//youtube.com/embed/" + parts[1]; 844 } 845 } 846 // 2) Transform URL Scheme: http://youtu.be/A9b9bxUyK0o 847 if (fileUrl.contains("youtu.be/")) { 848 String[] parts = fileUrl.split(".be/"); 849 String new_url_part = ""; 850 int indexOfParameters = parts[1].indexOf("&"); 851 if (parts[1] != null && indexOfParameters != -1) { 852 new_url_part = parts[1].substring(0, indexOfParameters); 853 new_url = "//youtube.com/embed/" + new_url_part; 854 } else if (parts[1] != null) { 855 new_url = "//youtube.com/embed/" + parts[1]; 856 } 857 } 858 return new_url; 859 } 860 861 /** 862 * Do a HTTP/S request to the configured moodle endpoint. 863 * Completes successfully and returns a String if a HTTP Status of 200 is returned. 864 * Throws RuntimeExceptions if Java SSL Keystore options or certificates are misconfigured. 865 * 866 * @return Moodle HTTP response body (String) 867 */ 868 private String callMoodle (String key, String functionName, String params) throws MoodleConnectionException { 869 Topic serviceUrl = getMoodleEndpointUrl(); 870 String endpointUri = serviceUrl.getSimpleValue().value().toString(); 871 String queryUrl = endpointUri + "?wstoken=" + key 872 + "&wsfunction=" + functionName + "&" + MOODLE_SERVICE_FORMAT; // "&service=" + MOODLE_SERVICE_NAME + 873 try { 874 if (isHTTPSConfigured()) { 875 // Many thanks to "Bruno" for formulating the following idea/approach at: 876 // http://stackoverflow.com/questions/10267968/error-when-opening-https-url-keycertsign-bit-is-not-set 877 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 878 KeyStore ks = KeyStore.getInstance("JKS"); 879 Topic client_jks_option = getJavaKeyStorePath(); 880 FileInputStream fis = new FileInputStream(client_jks_option.getSimpleValue().toString()); 881 ks.load(fis, getJavaKeyStorePass().getSimpleValue().toString().toCharArray()); 882 fis.close(); 883 tmf.init(ks); 884 // 885 SSLContext sslContext = SSLContext.getInstance("TLS"); 886 sslContext.init(null, tmf.getTrustManagers(), null); 887 // 888 HttpsURLConnection con = (HttpsURLConnection) new URL(queryUrl).openConnection(); 889 con.setSSLSocketFactory(sslContext.getSocketFactory()); 890 con.setRequestMethod("POST"); 891 con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 892 con.setRequestProperty("Content-Language", "en-US"); 893 con.setDoOutput(true); 894 con.setUseCaches (false); 895 con.setDoInput(true); 896 DataOutputStream wr = new DataOutputStream (con.getOutputStream()); 897 wr.writeBytes (params); 898 wr.flush(); 899 wr.close(); 900 if (con.getResponseCode() != 200) { 901 logger.warning("MoodleConnection HTTP Status \"" + con.getResponseCode() + "\""); 902 throw new MoodleConnectionException("MoodleConnection has responsed with a HTTP Status ", 903 con.getResponseCode()); 904 } 905 // Get Response 906 InputStream is = con.getInputStream(); 907 BufferedReader rd = new BufferedReader(new InputStreamReader(is)); 908 String line; 909 StringBuilder response = new StringBuilder(); 910 while((line = rd.readLine()) != null) { 911 response.append(line); 912 response.append('\r'); 913 } 914 rd.close(); 915 // Handle empty response 916 if (response.toString().isEmpty()) { 917 String message = "MoodleConnection Response was empty. This happens even if webservice features " 918 + "are completely deactivated or the function is not part of the \"External services\" " 919 + "definition."; 920 logger.warning(message); 921 throw new MoodleConnectionException(message, 204); 922 } 923 return response.toString(); 924 } else { // No HTTPS configured 925 logger.severe("You can but you should never make reqests to your moodle installation on behalf of " 926 + "your users unencrypted. Please configure HTTPS at your moodle web service endpoint."); 927 HttpURLConnection con = (HttpURLConnection) new URL(queryUrl).openConnection(); 928 con.setRequestMethod("POST"); 929 con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 930 con.setRequestProperty("Content-Language", "en-US"); 931 con.setDoOutput(true); 932 con.setUseCaches (false); 933 con.setDoInput(true); 934 DataOutputStream wr = new DataOutputStream (con.getOutputStream()); 935 wr.writeBytes (params); 936 wr.flush(); 937 wr.close(); 938 if (con.getResponseCode() != 200) { // this case never occurred to me 939 logger.warning("MoodleConnection HTTP Status \"" + con.getResponseCode() + "\""); 940 throw new MoodleConnectionException("MoodleConnection has responsed with a HTTP Status ", 941 con.getResponseCode()); 942 } 943 //Get Response 944 InputStream is = con.getInputStream(); 945 BufferedReader rd = new BufferedReader(new InputStreamReader(is)); 946 String line; 947 StringBuilder response = new StringBuilder(); 948 while((line = rd.readLine()) != null) { 949 response.append(line); 950 response.append('\r'); 951 } 952 rd.close(); 953 // Handle empty response 954 if (response.toString().isEmpty()) { 955 String message = "MoodleConnection Response was empty. This happens even if webservice features " 956 + "are completely deactivated or the function is not part of the \"External services\" " 957 + "definition."; 958 logger.warning(message); 959 throw new MoodleConnectionException(message, 204); 960 } 961 return response.toString(); 962 } 963 } catch (KeyManagementException ex) { 964 logger.warning("Moodle KeyManagementException " + ex.getMessage()); 965 throw new RuntimeException(ex); 966 } catch (CertificateException ex) { 967 logger.warning("Moodle CertificateException " + ex.getMessage()); 968 throw new RuntimeException(ex); 969 } catch (KeyStoreException ex) { 970 logger.warning("Moodle KeyStoreException " + ex.getMessage()); 971 throw new RuntimeException(ex); 972 } catch (NoSuchAlgorithmException ex) { 973 logger.warning("Moodle Cypher NoSuchAlgorithmException " + ex.getMessage()); 974 throw new RuntimeException(ex); 975 } catch (MoodleConnectionException ex) { 976 logger.warning("Moodle ConnectionException " + ex.message + "(" + ex.status + ")"); 977 throw new MoodleConnectionException(ex.message, ex.status); 978 } catch (MalformedURLException ml) { 979 logger.warning("Moodle Malformed URL Exception .. " + ml.getMessage().toString()); 980 throw new MoodleConnectionException("DeepaMehta could not connect to malformed url: \"" + queryUrl + "\"", 981 404); 982 } catch (IOException ex) { 983 logger.warning("Moodle I/O Exception .. " + ex.getMessage().toString()); 984 throw new MoodleConnectionException("DeepaMehta could not connect to \"" + queryUrl + "\"", 500); 985 } 986 } 987 988 private Topic checkAuthorization() { 989 String username = aclService.getUsername(); 990 if (username == null) throw new WebApplicationException(401); 991 return getUserAccountTopic(username); 992 } 993 994 private Topic getUserAccountTopic(String username) { 995 Topic accountTopic = null; 996 Topic userTopic = dms.getTopic(USER_NAME_TYPE_URI, new SimpleValue(username)).loadChildTopics(); 997 accountTopic = userTopic.getRelatedTopic(COMPOSITION_TYPE_URI, CHILD_ROLE_TYPE_URI, PARENT_ROLE_TYPE_URI, 998 USER_ACCOUNT_TYPE_URI); 999 return accountTopic; 1000 } 1001 1002 private String getMoodleSecurityKeyWithoutAuthCheck(Topic userAccount) { 1003 if (userAccount.hasProperty(MOODLE_SECURITY_KEY_URI)) { 1004 String token = (String) userAccount.getProperty(MOODLE_SECURITY_KEY_URI); 1005 return token; 1006 } 1007 return null; 1008 } 1009 1010 private Topic getMoodleEndpointUrl() { 1011 return dms.getTopic("uri", new SimpleValue(CLIENT_OPTION_ENDPOINT_URL_URI)).loadChildTopics(); 1012 } 1013 1014 private Topic getJavaKeyStorePath() { 1015 return dms.getTopic("uri", new SimpleValue(CLIENT_OPTION_JAVA_KEY_STORE_PATH)).loadChildTopics(); 1016 } 1017 1018 private Topic getJavaKeyStorePass() { 1019 return dms.getTopic("uri", new SimpleValue(CLIENT_OPTION_JAVA_KEY_STORE_PASS)).loadChildTopics(); 1020 } 1021 1022 private boolean isHTTPSConfigured() { 1023 Topic string_opt = dms.getTopic("uri", new SimpleValue(CLIENT_OPTION_USE_HTTPS)).loadChildTopics(); 1024 if (string_opt.getSimpleValue().toString().equals("true") || 1025 string_opt.getSimpleValue().toString().equals("True")) return true; 1026 return false; 1027 } 1028 1029 1030 1031 // -- 1032 // --- Workspace Assignments & ACL Fixes 1033 // -- 1034 1035 private void assignToMoodleWorkspace(Topic topic) { 1036 if (hasAnyWorkspace(topic)) { 1037 logger.warning("NOT assigning to Moodle Workspace since topic HAS some assigned already .."); 1038 return; 1039 } 1040 Topic moodleWorkspace = dms.getTopic("uri", new SimpleValue(WS_MOODLE_URI)); 1041 dms.createAssociation(new AssociationModel(AGGREGATION_TYPE_URI, 1042 new TopicRoleModel(topic.getId(), PARENT_ROLE_TYPE_URI), 1043 new TopicRoleModel(moodleWorkspace.getId(),CHILD_ROLE_TYPE_URI) 1044 )); 1045 } 1046 1047 private boolean hasAnyWorkspace(Topic topic) { 1048 return topic.getRelatedTopics(AGGREGATION_TYPE_URI, PARENT_ROLE_TYPE_URI, CHILD_ROLE_TYPE_URI, 1049 "dm4.workspaces.workspace", 0).getSize() > 0; 1050 } 1051 1052 private boolean associationExists(String edge_type, Topic item, Topic user) { 1053 List<Association> results = dms.getAssociations(item.getId(), user.getId(), edge_type); 1054 return (results.size() > 0); 1055 } 1056 1057 private DeepaMehtaObject setDefaultMoodleGroupACLEntries(DeepaMehtaObject item) { 1058 // Let's repair broken/missing ACL-Entries 1059 ACLEntry writeList = new ACLEntry(Operation.WRITE, UserRole.MEMBER, UserRole.CREATOR, UserRole.OWNER); 1060 aclService.setACL(item, new AccessControlList(writeList)); 1061 aclService.setCreator(item, USERNAME_OF_SETTINGS_ADMINISTRATOR); 1062 aclService.setOwner(item, USERNAME_OF_SETTINGS_ADMINISTRATOR); 1063 return item; 1064 } 1065 1066 private DeepaMehtaObject setDefaultMoodleAdminACLEntries(DeepaMehtaObject item) { 1067 // Let's repair broken/missing ACL-Entries 1068 ACLEntry writeEntry = new ACLEntry(Operation.WRITE, UserRole.CREATOR, UserRole.OWNER); 1069 aclService.setACL(item, new AccessControlList(writeEntry)); 1070 aclService.setCreator(item, USERNAME_OF_SETTINGS_ADMINISTRATOR); 1071 aclService.setOwner(item, USERNAME_OF_SETTINGS_ADMINISTRATOR); 1072 return item; 1073 } 1074 1075}