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 }