Adding a XWikirights Object to new pages conditionally

Continuing the discussion from Permissions: Add but not edit:

I’m currently looking for a mechanism that would allow to set dynamic rights to newly created pages.
More specificially, whenever a page is created whose name follows a pattern (_Homepage):

  • The Groupname should be extracted from the pagename
  • An XWikiRights-Object should be created for the page which allows the extracted Group access to the document

As @tmortagne pointed out in the linked post, this can be done via an Event Listener.

I’ve followed http://extensions.xwiki.org/xwiki/bin/view/Extension/WikiComponent%20Module to create a Listener as a Component.

I also added the following groovy-code to the onEvent method:

{{groovy}}
import org.xwiki.context.*
import org.xwiki.observation.*
import org.xwiki.observation.event.*
import org.xwiki.bridge.event.*
import org.xwiki.model.reference.*
import org.xwiki.model.*
import com.xpn.xwiki.web.*
import com.xpn.xwiki.*

try {
	System.out.println("\n[BEGIN]\nThe document ${xcontext.method.input.get(1)} has been created.")

	def crtContext = Utils.getComponent(Execution.class).getContext().getProperty("xwikicontext")
	def docSource = xcontext.method.input.get(1)
	def oldDoc = docSource.getOriginalDocument()

	def existingObj = oldDoc.getObject("XWiki.XWikiRights")

	if (existingObj == null) {
		System.out.println("XWikiRights object does not exist, creating.")
		def rightsObj = oldDoc.getObject("XWiki.XWikiRights", true, crtContext)  

		// try to read props from obj
		def allow = rightsObj.get("allow")
		def users = rightsObj.get("users")
		def levels = rightsObj.get("levels")

		System.out.println("Setting value of 'allow'.")
		rightsObj.setIntValue("allow", 1)

		System.out.println("Setting value of 'levels'.")
		rightsObj.setStringValue("levels", "view,edit")

		System.out.println("Setting value of 'users'.")
		rightsObj.setLargeStringValue("users", "XWiki.DigitalData")

		System.out.print("\nallow:\t")
		System.out.println(rightsObj.get("allow").value)
		System.out.print("levels:\t")
		System.out.println(rightsObj.get("levels").value)
		System.out.print("users:\t")
		System.out.println(rightsObj.get("users").value)

		oldDoc.setObject("XWiki.XWikiRights", 0, rightsObj)
		System.out.println("\nXWikiRights object added.\n[END]")
	} else {
		System.out.println("XWikiRights object exists.")
		System.out.println(existingObj)
	}
} catch(e) {
	System.out.println("ERROR!")
	System.out.println(e.getMessage())
}
{{/groovy}}


EDIT

I added the code I’ve written so far.

I can check for an existing XWikiRights object and also create a new one. Setting the property works fine too.
What does not work is actually saving the object back to the page.

  • Do I have to call the setObject on the document?
  • Am I using the method correctly?
  • Is there something else wrong with my code that prevents creating the XWikiRights object?

Things I’ve been reading:

Thanks in advance!

oldDoc.save()

Pascal B

@Pbas: Thanks for your answer. However, when I call save on oldDoc (or docSource), I get an error saying that the save method does not exist:

No signature of method: com.xpn.xwiki.doc.XWikiDocument.save() is applicable for argument types: () values: []
Possible solutions: wait(), any(), wait(long), sleep(long), any(groovy.lang.Closure), isCase(java.lang.Object)

k maybe I’ m wrong (because I always use groovy script inside a wiki page) but maybe you must work with xwiki api to avoid your error:

http://lists.xwiki.org/pipermail/users/2009-January/010546.html

As you see, that is not an api object, but an internal class
(com.xpn.xwiki.doc.XWikiDocument vs. com.xpn.xwiki.api.Document). This
means that API methods don’t work, unless they have an equivalent in the
other class.

You can either look at the methods of the XWikiDocument class, or get an
API object and work with it (new com.xpn.xwiki.apiDocument(tmpDoc)

It’s Document.save() or XWiki.saveDocument(...)

See XWiki Platform - Old Core 10.1 API

I get the following error when I add this line: XWiki.saveDocument(oldDoc, "Added Rights", crtContext):

No signature of method: static com.xpn.xwiki.XWiki.saveDocument() is applicable for argument types: 
(com.xpn.xwiki.doc.XWikiDocument,java.lang.String, com.xpn.xwiki.XWikiContext) values: 
[XWiki.FTSolutionsTemplateProvider.EventListener,Added Rights, ...]
Possible solutions: 
saveDocument(com.xpn.xwiki.doc.XWikiDocument, java.lang.String, com.xpn.xwiki.XWikiContext), 
saveDocument(com.xpn.xwiki.doc.XWikiDocument, com.xpn.xwiki.XWikiContext), 
saveDocument(com.xpn.xwiki.doc.XWikiDocument, java.lang.String, boolean, com.xpn.xwiki.XWikiContext), 
getDocument(com.xpn.xwiki.doc.XWikiDocument, java.lang.String, com.xpn.xwiki.XWikiContext), 
getDocument(com.xpn.xwiki.doc.XWikiDocument, com.xpn.xwiki.XWikiContext), getDocument(java.lang.String, com.xpn.xwiki.XWikiContext)

To me the signatures look the same. (XWikiDocument, String, XWikiContext)


Another thing I’ve tried was using the SaveAction Class:

def action = new SaveAction()
action.save()

Which led to an endless loop where:

  • The doc was saved by me
  • This triggered the listener which saved the document
  • This triggered the listener which saved the document
  • This triggered the listener which saved the document

This suggests to me that I don’t event have to explicitly trigger the save… I’m confused :confused:

XWiki.saveDocument() is NOT a static method :slight_smile:

You need to use the xwiki binding from Groovy.

Thanks for the tip, @vmassol!
I’ve trimmed down my script to the bare-minimum to get the save working:

{{groovy}}
import org.xwiki.context.*
import org.xwiki.observation.*
import org.xwiki.observation.event.*
import org.xwiki.bridge.event.*
import org.xwiki.model.reference.*
import org.xwiki.model.*
import com.xpn.xwiki.web.*
import com.xpn.xwiki.api.*

try {
	System.out.println("\n[BEGIN]\nThe document ${xcontext.method.input.get(1)} has been created.")

	def docSource = xcontext.method.input.get(1)
        def document = docSource.getOriginalDocument()
        def context = xcontext.method.input.get(2)
        def xwiki = context.getWiki()
        document.setContent("${document.getContent()}\n* ${document.fullName} has been modified!")
        //xwiki.saveDocument(document, "Logging event", true, context)
	System.out.println(document.fullName)

} catch(e) {
	System.out.println("ERROR!")
	System.out.println(e.getMessage())
}
{{/groovy}}

When I uncomment the xwiki.saveDocument lin, whenever I save the document (from the wysiwyg editor), I’m stuck in a loop where the document is being saved over and over before giving up eventually.

My main problem seems to be that I want to change the document that created the event. To save my changes I need to call saveDocument which triggers my listener again… :dizzy_face:


I’ve found and example where saving a document changes another document and appends some text to it. Here the problem is solved by not exiting the listener when the other document (Main.Logs) is saved:

    void onEvent(Event event, Object source, Object context)
   {
       // Prevent infinite recursion since in this example we log to wiki page which 
       // triggers a document change... :)
       if (source.fullName != "Main.Logs") {
            def xwiki = context.getWiki()
            def document = xwiki.getDocument(logReference, context)
            document.setContent("${document.getContent()}\n* ${source.fullName} has been modified!")
            xwiki.saveDocument(document, "Logging event", true, context)
       }
   }

I’m beginning to wonder: Is there even a way to edit the document that was just saved without getting in to that save-loop? :confused:

Okay, the problem was not saving the document, but I was operating on the wrong document altogether :sweat_smile:

This works:

def context = xcontext.method.input.get(2)
def xwiki = context.getWiki()
def document = xwiki.getDocument(docSource, context)

When I create a new XWikiRights Object for document and set properties on it, the Object is added to the document without the need to call saveDocument

if you modify a document you must absolutely save the doc or it won’t be saved. You may think it works because it’s in the doc cache but if you restart your wiki you’ll see it hasn’t been saved.

I’ve just restarted the wiki and the XWikiRights Object is still there.

I’m not actually trying to change the content of the document but add an XObject(?) of type XWikiRights to it.
I do it in the onEvent method of the Listener that listens to the DocumentCreatedEvent.

ah ok. Now, DocumentCreatedEvent means that the doc has been created and saved already… DocumentCreatingEvent is the one before the save… So normally it shouldn’t be saved in your case.

I’ve tested it with DocumentCreatedEvent and DocumentCreatingEvent and both seem to work.

If you use DocumentCreatingEvent then the modifications are saved automatically for you in the same revision. If you use DocumentCreatedEvent then your listener is called after the save and you need to call save() again (also adding a new revision). If you don’t call save() then restarting your wiki should loose the change (it’ll be just in the memory doc cache).

I’m basically facing the same challenge. After restarting XWiki without calling saveDocument my changes are lost.

Calling saveDocument however starts an infinite loop, since the listener is triggered again.

Any ideas on how to prevent that?

The code itself is part of an events listener which listens to DocumentUpdatedEvent():

{{groovy}}
import org.xwiki.context.*
import org.xwiki.observation.*
import org.xwiki.observation.event.*
import org.xwiki.bridge.event.*
import org.xwiki.model.reference.*
import org.xwiki.model.*
import com.xpn.xwiki.web.*
import com.xpn.xwiki.api.*

try {
	
	def docSource = xcontext.method.input.get(1)
        def document = docSource.getOriginalDocument()
        def context = xcontext.method.input.get(2)
        def xwiki = context.getWiki()
	//def document = xwiki.getDocument(docSource, context)
    
	def userObject = document.getObject("XWiki.XWikiUsers")
	def rightsObject = document.getObject("XWiki.XWikiRights", true, context)
	
	// Check if updated page is a profile page
	if (userObject == null) {
		// Nothing
	} else {

	rightsObject.setLargeStringValue("groups", "XWiki.PersonalAdminGroup")
	rightsObject.setStringValue("levels", "view,edit")
	rightsObject.setLargeStringValue("users", "XWiki.TestUser,XWiki.TestUser2")
	rightsObject.setIntValue("allow", 1)

	document.setObject("XWiki.XWikiRights", 0, rightsObject)

	// Saving changes starts infinite loop
	//xwiki.saveDocument(document, "Rights set", true, context)
	}
} catch(e) {
	System.out.println("ERROR!")
	System.out.println(e.getMessage())
}
{{/groovy}}

I did not followed that thread in details but when you want to modify a saved document you should do it before it’s saved by listening to DocumentUpdatingEvent and by modifying the XWikiDocument instance you get as parameter. If you do it after you create new versions which is not very nice, impact the performances (save is expensive) and indeed can cause an infinite loop in your listener if you are not careful before the each save will call your listener over and over again.

I fully agree with all of your points.

Unfortunately the problem runs a bit deeper. What I’m trying to achieve is a mechanism for some users to easily and flexible set edit rights on profile pages.

We have the need for restricted access to upload an download documents on user profiles.

Therefore I added some properties on the User Class to easily select a list of users which then shall be taken into account as a variable when setting the RightsObject.

However, I already tried my luck with the DocumentUpdatingEvent and the RightsObject doesn’t get set properly without the saveDocument which then leads to a loop again :frowning:

It should definitely work just fine and it’s used a lot AFAIK. The XWikiDocument instance sent as second argument (you are modifying that, right ? Not the original document ? since that’s what do do in the last code you pasted and it does not make much sense) with the DocumentUpdatingEvent is definitely the one sent to the store so any modification to it will be applied (unless a following DocumentUpdatingEvent listener remove them from the document but I guess you don’t have that).

Already tried it that way, too. But for some reason the created RightsObject is empty.

Then sometimes after reloading the profile it shows as expected. But then again loading of the profile triggers another DocumentUpdatingEvent probably from the LDAP sync :man_shrugging:

{{groovy}}
import org.xwiki.context.*
import org.xwiki.observation.*
import org.xwiki.observation.event.*
import org.xwiki.bridge.event.*
import org.xwiki.model.reference.*
import org.xwiki.model.*
import com.xpn.xwiki.web.*
import com.xpn.xwiki.api.*

try {
	
	def docSource = xcontext.method.input.get(1)
        def context = xcontext.method.input.get(2)
        def xwiki = context.getWiki()
	def document = xwiki.getDocument(docSource, context)
    
	def userObject = document.getObject("XWiki.XWikiUsers")
	
	// Check if updated page is a profile page
	if (userObject == null) {
		// Nothing
	} else {

	def rightsObject = document.getObject("XWiki.XWikiRights", true, context)
	
	rightsObject.setLargeStringValue("groups", "XWiki.PersonalAdminGroup")
	rightsObject.setStringValue("levels", "view,edit")
	rightsObject.setLargeStringValue("users", "XWiki.TestUser,XWiki.TestUser2")
	rightsObject.setIntValue("allow", 1)

	document.setObject("XWiki.XWikiRights", 0, rightsObject)

	}
} catch(e) {
	System.out.println("ERROR!")
	System.out.println(e.getMessage())
}
{{/groovy}}

That’s not what I said :slight_smile:

You are supposed to modify docSource itself which is a XWikiDocument instance already, that’s the XWikiDocument instance which is actually sent to the store. XWiki#getDocument depending on the use case may return you a XWikiDocument which is different from the one actually saved (in short some code directly modify the XWikiDocument from the cache before saving it and other clone it first to be safe).

1 Like