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