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