Ticket #569 (closed Task: fixed)

Opened 7 years ago

Last modified 6 years ago

Question: What must be returned by a custom page_renderer in render_form

Reported by: Malte Owned by: jri
Priority: Minor Milestone:
Component: DeepaMehta Standard Distribution Version: 4.1.2
Keywords: Cc:
Complexity: 3 Area: Application Framework / API
Module: deepamehta-webclient

Description

For the moodle-plugin I wrote a custom page_renderer. The aim of that is to allow just tagging of "Moodle Items" (but no editing of any other values). Despite the fact that I am wondering on how to do this best, currently, when I press "OK" on editing such an item and in the custom_renderer implementation just return what I received (as a param in render_form(some_model_item), most of the time I get this error:

02.12.2013 18:29:40 de.deepamehta.core.impl.EmbeddedService updateTopic
WARNUNG: ROLLBACK!
02.12.2013 18:29:40 de.deepamehta.plugins.webservice.provider.RuntimeExceptionMapper toResponse
SCHWERWIEGEND: A resource method or event listener threw a RuntimeException. Mapped exception to response: 500 (Internal Server Error).
java.lang.RuntimeException: Updating topic failed (topic (id=18512, uri="null", typeUri="org.deepamehta.resources.resource", value="null", composite={org.deepamehta.reviews.score=topic (id=18566, uri="null", typeUri="org.deepamehta.reviews.score", value="6", composite={}), org.deepamehta.resources.last_modified_at=topic (id=18548, uri="null", typeUri="org.deepamehta.resources.last_modified_at", value="1381424316699", composite={}), org.deepamehta.resources.author=topic (id=4705, uri="", typeUri="org.deepamehta.resources.author", value="", composite={}), org.deepamehta.resources.license_jurisdiction=topic (id=2620, uri="", typeUri="org.deepamehta.resources.license_jurisdiction", value="", composite={}), org.deepamehta.resources.blocked_for_edits=topic (id=18575, uri="null", typeUri="org.deepamehta.resources.blocked_for_edits", value="false", composite={}), dm4.webbrowser.web_resource=[topic (id=-1, uri="null", typeUri="dm4.webbrowser.web_resource", value="null", composite={dm4.webbrowser.web_resource_description=topic (id=-1, uri="null", typeUri="dm4.webbrowser.web_resource_description", value="", composite={}), dm4.webbrowser.url=topic (id=-1, uri="null", typeUri="dm4.webbrowser.url", value="", composite={})})], org.deepamehta.resources.is_published=topic (id=18557, uri="null", typeUri="org.deepamehta.resources.is_published", value="true", composite={}), dm4.tags.tag=[topic (id=15930, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=15827, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=11575, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=40190, uri="", typeUri="dm4.tags.tag", value="", composite={})], org.deepamehta.resources.created_at=topic (id=18539, uri="null", typeUri="org.deepamehta.resources.created_at", value="1381424316699", composite={}), org.deepamehta.resources.content=topic (id=18522, uri="null", typeUri="org.deepamehta.resources.content", value="<p>Durchführung des Experiments Homogenes Gleichgewicht</p><p><br></p><div class="embeded"><iframe src="http://www.youtube.com/embed/WzG7MBeAJEs?wmode=transparent&amp;jqoemcache=LnCIT" width="425" height="349" allowfullscreen="true" allowscriptaccess="always" scrolling="no" frameborder="0"></iframe><br></div>", composite={}), org.deepamehta.resources.name=topic (id=18513, uri="null", typeUri="org.deepamehta.resources.name", value="", composite={}), org.deepamehta.resources.license=topic (id=2617, uri="", typeUri="org.deepamehta.resources.license", value="", composite={})}))
	at de.deepamehta.core.impl.EmbeddedService.updateTopic(EmbeddedService.java:187)
	at de.deepamehta.plugins.webservice.WebservicePlugin.updateTopic(WebservicePlugin.java:94)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60)
	at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$TypeOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:185)
	at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75)
	at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:302)
	at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
	at com.sun.jersey.server.impl.uri.rules.ResourceObjectRule.accept(ResourceObjectRule.java:100)
	at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
	at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84)
	at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1480)
	at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1411)
	at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1360)
	at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1350)
	at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416)
	at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:538)
	at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:716)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
	at org.apache.felix.http.base.internal.handler.ServletHandler.doHandle(ServletHandler.java:96)
	at org.apache.felix.http.base.internal.handler.ServletHandler.handle(ServletHandler.java:79)
	at org.apache.felix.http.base.internal.dispatch.ServletPipeline.handle(ServletPipeline.java:42)
	at org.apache.felix.http.base.internal.dispatch.InvocationFilterChain.doFilter(InvocationFilterChain.java:49)
	at org.apache.felix.http.base.internal.dispatch.HttpFilterChain.doFilter(HttpFilterChain.java:33)
	at org.apache.felix.http.base.internal.dispatch.FilterPipeline.dispatch(FilterPipeline.java:48)
	at org.apache.felix.http.base.internal.dispatch.Dispatcher.dispatch(Dispatcher.java:39)
	at org.apache.felix.http.base.internal.DispatcherServlet.service(DispatcherServlet.java:67)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
	at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
	at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:390)
	at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
	at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
	at org.mortbay.jetty.Server.handle(Server.java:326)
	at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
	at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
	at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
	at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
	at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
	at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)
	at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: java.lang.RuntimeException: Updating composite value of topic 18512 failed (newComp={org.deepamehta.reviews.score=topic (id=18566, uri="null", typeUri="org.deepamehta.reviews.score", value="6", composite={}), org.deepamehta.resources.last_modified_at=topic (id=18548, uri="null", typeUri="org.deepamehta.resources.last_modified_at", value="1381424316699", composite={}), org.deepamehta.resources.author=topic (id=4705, uri="", typeUri="org.deepamehta.resources.author", value="", composite={}), org.deepamehta.resources.license_jurisdiction=topic (id=2620, uri="", typeUri="org.deepamehta.resources.license_jurisdiction", value="", composite={}), org.deepamehta.resources.blocked_for_edits=topic (id=18575, uri="null", typeUri="org.deepamehta.resources.blocked_for_edits", value="false", composite={}), dm4.webbrowser.web_resource=[topic (id=-1, uri="null", typeUri="dm4.webbrowser.web_resource", value="null", composite={dm4.webbrowser.web_resource_description=topic (id=-1, uri="null", typeUri="dm4.webbrowser.web_resource_description", value="", composite={}), dm4.webbrowser.url=topic (id=-1, uri="null", typeUri="dm4.webbrowser.url", value="", composite={})})], org.deepamehta.resources.is_published=topic (id=18557, uri="null", typeUri="org.deepamehta.resources.is_published", value="true", composite={}), dm4.tags.tag=[topic (id=15930, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=15827, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=11575, uri="", typeUri="dm4.tags.tag", value="", composite={}), topic (id=40190, uri="", typeUri="dm4.tags.tag", value="", composite={})], org.deepamehta.resources.created_at=topic (id=18539, uri="null", typeUri="org.deepamehta.resources.created_at", value="1381424316699", composite={}), org.deepamehta.resources.content=topic (id=18522, uri="null", typeUri="org.deepamehta.resources.content", value="<p>Durchführung des Experiments Homogenes Gleichgewicht</p><p><br></p><div class="embeded"><iframe src="http://www.youtube.com/embed/WzG7MBeAJEs?wmode=transparent&amp;jqoemcache=LnCIT" width="425" height="349" allowfullscreen="true" allowscriptaccess="always" scrolling="no" frameborder="0"></iframe><br></div>", composite={}), org.deepamehta.resources.name=topic (id=18513, uri="null", typeUri="org.deepamehta.resources.name", value="", composite={}), org.deepamehta.resources.license=topic (id=2617, uri="", typeUri="org.deepamehta.resources.license", value="", composite={})})
	at de.deepamehta.core.impl.AttachedCompositeValue.update(AttachedCompositeValue.java:278)
	at de.deepamehta.core.impl.AttachedDeepaMehtaObject.update(AttachedDeepaMehtaObject.java:186)
	at de.deepamehta.core.impl.AttachedTopic.update(AttachedTopic.java:100)
	at de.deepamehta.core.impl.EmbeddedService.updateTopic(EmbeddedService.java:181)
	... 43 more
Caused by: java.lang.NullPointerException
	at de.deepamehta.core.impl.ValueStorage.isReferenceByUri(ValueStorage.java:174)
	at de.deepamehta.core.impl.ValueStorage.isReference(ValueStorage.java:166)
	at de.deepamehta.core.impl.AttachedCompositeValue.updateAggregationMany(AttachedCompositeValue.java:473)
	at de.deepamehta.core.impl.AttachedCompositeValue.updateChildTopics(AttachedCompositeValue.java:300)
	at de.deepamehta.core.impl.AttachedCompositeValue.update(AttachedCompositeValue.java:272)

(A) What is it that I might be doing wrong?
(B) How could I achieve my aim at best? (I have a custom renderer for tags already but I currently do not see a way on how I could use that within a page_renderer, since I do not see a way on how to get this page_renderer right in first place).

Thanks for your help.

Change History

comment:1 Changed 7 years ago by jri

I guess you have an aggregated composite in your data model, right?
Look if you find any hints in #30 (see also #337).

comment:2 Changed 7 years ago by jri

Try to put empty URI strings in the many aggregated child topics contained in the topic model passed to dm4c.do_update_topic().

comment:3 follow-up: ↓ 4 Changed 7 years ago by Malte

Thanks for your hint that did the trick. In a page_renderer render_form return function one needs that dm4c.do_update_topic()-call which is different compared to when implementing a multi_renderer and while adapting my multi_renderer to become a page_renderer I just stumbled shortly upon the (there missing) .object in the respective renderer model.

Here is the complete return function of my strange page_renderer for the record:

            // set up some form

            return function () {

                var tags = []
                var enteredTags = getTagsSubmitted("input.tags")
                var tagsToReference = []

                // 0) create new and collect existing tags
                for (var label in enteredTags) {
                    var name = enteredTags[label]
                    var tag = getLabelContained(name, allAvailableTags)
                    if (tag == undefined) {
                        var newTag = dm4c.create_topic(TAG_URI, {"dm4.tags.label": name, "dm4.tags.definition" : ""})
                        tagsToReference.push(newTag)
                    } else {
                        tagsToReference.push(tag)
                    }
                }

                // 1) identify all tags to be deleted
                for (var existingTag in existingTags) {
                    var element = existingTags[existingTag].value // this differs here from multi_renderer (no .object)
                    var elementId = existingTags[existingTag].id // this differs here from multi_renderer (no .object)
                    if (getLabelContained(element, tagsToReference) == undefined) {
                        tags.push( dm4c.DEL_PREFIX + elementId ) // not tags.push({"id" : dm4c.DEL_PREFIX + elementId})
                    }
                }

                // 2) returning reference all new and existing tags
                for (var item in tagsToReference) {
                    var topic_id = tagsToReference[item].id
                    if (topic_id != -1) {
                        tags.push( dm4c.REF_PREFIX + topic_id ) // not tags.push({"id" : dm4c.REF_PREFIX + topic_id})
                    }
                }

                // 3) assemble new topic model
                topic.composite['dm4.tags.tag'] = tags
                // where this array contents simply look like this (no json-objects to be constructed)
                // [ "del_id:40190", "ref_id:51291", "ref_id:51131", "ref_id:11318"]

                // 4) this call is needed when implementing page_renderers
                dm4c.do_update_topic(topic)

                return topic // is this still needed then?
            }

Thanks to your help all Moodle Items (materials and activities) processed with DM's mapping-moodle-plugin can now be tagged.

I think, given that moodle-systems do not provide any helpful thematical order features, enabling students and teachers to connect learning materials (and activities) across weeeks (or even courses) in freeform-style sounds like a good idea. Looking forward to advance the moodle case even more.

Cheers!

comment:4 in reply to: ↑ 3 ; follow-up: ↓ 6 Changed 7 years ago by jri

Replying to Malte:

Thanks for your hint that did the trick.

Nice to hear!
Actually, setting empty URIs seems useless. Currently it is required as a workaround. I should fix the Core accordingly.

In a page_renderer render_form return function one needs that dm4c.do_update_topic()-call which is different compared to when implementing a multi_renderer and while adapting my multi_renderer to become a page_renderer I just stumbled shortly upon the (there missing) .object in the respective renderer model.

Note: when implementing a page renderer you are responsible for 3 things (that reflects the contract between you and the Page Panel):

  • Render a given topics info.
  • Render a given topics form.
  • Provide the function the Page Panel calls when the user saves a form.

The Page Panel can't know how to save your form input because it can't know your underlying form model. In a page renderer you have the freedom to utilize any model, so its up to you to do the actual saving, that is calling dm4c.do_update_topic() in *your* particular case.

That is also the reason why there is no .object in a page renderer. This is a property of a Page Model. And Page Model in turn is a feature introduced by the standard page renderers (topic_renderer and association_renderer). The entire framework of simple renderers and multi renderers is rooted in the standard page renderers. So in a simple/multi renderer you get a page model passed.

So, Page Model is the common model underlying the standard page renderers. And the simple/multi renderer extension facility is provided only by the standard page renderers. Simple and multi renderers, as well as the underlying common page model exist/live only within the standard page renderers.

When you implement a custom page renderer, its up to you weather you define a extension facility for that renderer. In that case you would come up with your own abstraction of a page model.

So every time when you want provide a custom Page Panel rendering you can choose:

  • if your rendering and form processing logic fits the standard page model then you just provide simple renderers and multi renderers. You must NOT provide the actual form processing (saving) logic but only the data to be saved in regard of the standard page model.
  • if you want full control about the Page Panel rendering and form processing then you provide your own page renderer implementation.

Here is the complete return function of my strange page_renderer for the record:

            // set up some form

            return function () {

                var tags = []
                var enteredTags = getTagsSubmitted("input.tags")
                var tagsToReference = []

                // 0) create new and collect existing tags
                for (var label in enteredTags) {
                    var name = enteredTags[label]
                    var tag = getLabelContained(name, allAvailableTags)
                    if (tag == undefined) {
                        var newTag = dm4c.create_topic(TAG_URI, {"dm4.tags.label": name, "dm4.tags.definition" : ""})

Note: you don't have to create new tags with multiple calls (multiple requests!) to dm4c.create_topic().
Instead you can add the new tag topics in your tags array, setting the ID to -1 and the URI to empty.
The server-side updateTopic logic then creates the tag topics and does the assignments to the parent topic.

See updateAggregationMany() in AttachedCompositeValue. 3 cases are supported:

  • delete existing assignments (when DEL_PREFIX is found)
  • create assignments to existing topics (when REF_PREFIX is found)
  • create new topic and assign it (when no prefix is found. In this case an entire topic object is expected (with ID -1 and URI ""))

So you can do the complete update with 1 request.
Try it!

                        tagsToReference.push(newTag)
                    } else {
                        tagsToReference.push(tag)
                    }
                }

                // 1) identify all tags to be deleted
                for (var existingTag in existingTags) {
                    var element = existingTags[existingTag].value // this differs here from multi_renderer (no .object)
                    var elementId = existingTags[existingTag].id // this differs here from multi_renderer (no .object)
                    if (getLabelContained(element, tagsToReference) == undefined) {
                        tags.push( dm4c.DEL_PREFIX + elementId ) // not tags.push({"id" : dm4c.DEL_PREFIX + elementId})
                    }
                }

                // 2) returning reference all new and existing tags
                for (var item in tagsToReference) {
                    var topic_id = tagsToReference[item].id
                    if (topic_id != -1) {
                        tags.push( dm4c.REF_PREFIX + topic_id ) // not tags.push({"id" : dm4c.REF_PREFIX + topic_id})
                    }
                }

                // 3) assemble new topic model
                topic.composite['dm4.tags.tag'] = tags
                // where this array contents simply look like this (no json-objects to be constructed)
                // [ "del_id:40190", "ref_id:51291", "ref_id:51131", "ref_id:11318"]

                // 4) this call is needed when implementing page_renderers
                dm4c.do_update_topic(topic)

                return topic // is this still needed then?
            }

Your returned function must not return anything. The caller (the Page Panel) would not know what to do with the value. No contract is defined here.

Thanks to your help all Moodle Items (materials and activities) processed with DM's mapping-moodle-plugin can now be tagged.

I think, given that moodle-systems do not provide any helpful thematical order features, enabling students and teachers to connect learning materials (and activities) across weeeks (or even courses) in freeform-style sounds like a good idea. Looking forward to advance the moodle case even more.

That's cool!

Cheers!

comment:5 Changed 7 years ago by jri

I filed an issue addressing the original NullPointerException? problem: #574

Don't hesitate if my explanations in the recent comment make sense to you.

comment:6 in reply to: ↑ 4 Changed 7 years ago by jri

Replying to jri:

Actually, setting empty URIs seems useless. Currently it is required as a workaround. I should fix the Core accordingly.

This is now fixed (#574).

Here is the complete return function of my strange page_renderer for the record:

            // set up some form

            return function () {

                var tags = []
                var enteredTags = getTagsSubmitted("input.tags")
                var tagsToReference = []

                // 0) create new and collect existing tags
                for (var label in enteredTags) {
                    var name = enteredTags[label]
                    var tag = getLabelContained(name, allAvailableTags)
                    if (tag == undefined) {
                        var newTag = dm4c.create_topic(TAG_URI, {"dm4.tags.label": name, "dm4.tags.definition" : ""})

Note: you don't have to create new tags with multiple calls (multiple requests!) to dm4c.create_topic().
Instead you can add the new tag topics in your tags array, setting the ID to -1 and the URI to empty.
The server-side updateTopic logic then creates the tag topics and does the assignments to the parent topic.

In the course of #574 setting ID -1 and an empty URI is not required anymore to create new child topics via a update request. Just omit the ID and the URI. At server-side a child topic without a ID is regarded as to be created.

See updateAggregationMany() in AttachedCompositeValue. 3 cases are supported:

  • delete existing assignments (when DEL_PREFIX is found)
  • create assignments to existing topics (when REF_PREFIX is found)
  • create new topic and assign it (when no prefix is found. In this case an entire topic object is expected (with ID -1 and URI ""))

In the course of #574 and #337 a 4th case is now supported:

  • update contents of an assigned topic (when no prefix is found AND the topic in the request has an ID attribute).

So you can do the complete update with 1 request.
Try it!

Yes, you really should try it.
Updating a complex topic (including all the 4 cases mentioned above) with a single update request.
Thats one of DM's intrinsic strong core features and I would like to hear if it is managable/understandable.

comment:7 Changed 7 years ago by jri

Hi Malte,
i saw your Malted/PluginDevelopmentNotes
it's nice.
The complex update-request syntax can be simplified, and a 4th case is available meanwhile.
See my recent comment:6

comment:8 Changed 7 years ago by jri

Do we want close this ticket?

comment:9 Changed 6 years ago by jri

  • Status changed from new to closed
  • Resolution set to fixed
Note: See TracTickets for help on using tickets.