API
@-Formulas
JavaScript
LotusScript
Reg Exp
Web Design
Notes Client
XPages
 
Soft Deletes And Response Documents
We've used soft deletes before, but never with response documents. So when our first project asking for that came about, it was time for some testing. And just like regular deletes can result in orphan documents, recovering soft-deleted documents can result in orphan documents. For example, let's say you've soft-deleted a document and its parent. If you restore the child, then the parent is still soft-deleted, so the child becomes an orphan in production. Conversely, if you restore the parent and not the child, that child, although not an orphan, might not be visible to your "deleted documents" views depending on how they were set up, so it might not be possible to restore that child.

There are many postings on the internet talking about preventing orphan documents by deleting all the children. We even have this document that describes our method. So we won't talk about that here. This tip will focus on the restoring of soft-deleted parents and children.

I'm going to assume that you either have used soft deletions before or have read the Notes Help. It's actually very easy to implement soft deletions. There's also a that you can download. The sample database has everything that we talk about in this article.

What we want to accomplish here is, when a document is restored, we want to restore all the related documents. For example, if a child document is restored, then restore its parent (if the parent has also been deleted). But since we're restoring a parent, we also want to delete all children of that parent or else those children could be "deleted orphans" (orphan documents that are still deleted, but you might not have any way of restoring them). If a grandchild is restored, then its parent must be restored and its grandparent must be restored (assuming they were all deleted in the first place).

Note that this is only talking about the relationship on the side of deleted documents. If you delete a child document and haven't deleted the parent, then there's no issue - you can restore the child document easily enough or let the system purge the deleted child after the fixed number of hours. This is only handling the times when you have deleted a parent and children and are now wanting to restore either the parent or a child.

There are a few stumbling blocks you run into when thinking about restoring children along with the parent and vice versa. The first problem you run into is that you're going to want to do all your processing in script (it is quite a bit easier than trying to do all this in formula, even with the looping available in 6), and there's no "restore" method in script. The help talks about @Command([EditRestoreDocument]), which can't be accessed in LotusScript. Luckily, there's a formula language function called @UndeleteDocument that can be accessed in script through the Evaluate statement. So we can use that to restore the documents in script.

The second problem you run into is that the deleted documents don't have a true parent-child relationship. For example, the deleted parent will always return Nothing if you look at the Responses property. So you need another way to get at the children of a document. We use a lookup view to accomplish this, which I'll talk about in a moment.

The third item you run into is dealing with multiple levels of restoring documents. Let's say you're restoring a child document that also has deleted children. You need to restore the parent and the selected document's children. Recursion could be used to handle this situation, but I use a While loop that goes through until everything has been found.

First, let's create the lookup view called Deleted UNID with an alias of vwDeleteUNID. The view is going to include deleted documents, and the view properties should be set to not include documents in a hierarchy. Take a look at figure 1 and figure 2 for examples. The view can be hidden if you want (it might actually be better to hide it since it is a lookup view). The first column of the view is really all that's needed - after that it's up to you. The first column should be sorted (either ascending or descending; it doesn't matter) and should have this formula:

@If(@IsAvailable($Ref); @Text($Ref); @Text(@DocumentUniqueID))

The view column will list either the unique ID of the document's parent (if it's a child) or the unique ID of the document itself (if it's a parent). Note that if the document is a child that also has children, the unique ID of its parent will be listed - the children will be picked up in another manner.

Next, in your view(s) where the administrators choose the documents to restore, you'll need to provide a button so they can restore the selected documents. This button should simply run the (Restore Documents) agent that we'll write next. So the button code is:

@Command([ToolsRunMacro]; "(Restore Documents)")

Finally, let's take a look at the agent. The agent should be set up to run from the agent list, and run on selected documents. I'll go through the code in blocks:

Sub Initialize
   Dim session As New NotesSession
   Dim db As NotesDatabase
   Dim coll As NotesDocumentCollection
   Dim doc As NotesDocument
   Dim view As NotesView
   Dim restore() As NotesDocument
   Dim i As Integer
   Dim uniqueUNID List As Byte
   Dim x As Integer
   Dim continue As Boolean
   Dim temp As Variant
   
   Set db = session.CurrentDatabase
   Set coll = db.UnprocessedDocuments
   Set view = db.GetView("vwDeleteUNID")

Set up all the variable names and get a handle to the documents selected in the view.

   i = 0
   Set doc = coll.GetFirstDocument
   While Not doc Is Nothing
      Redim Preserve restore(i)
      Set restore(i) = doc
      uniqueUNID(doc.UniversalID) = 1
      Set doc = coll.GetNextDocument(doc)
      i = i + 1
   Wend

Take all the selected documents and put them into an array. Keep track of the UNID of the selected documents, so they are only put into the array once. (We also use that list to find out if a document's parent is already in the list, so we know if we should include it or not).

   continue = True
   While continue
      continue = False
      For i = 0 To Ubound(restore)
         Set coll = view.GetAllDocumentsByKey(restore(i).UniversalID, True)
         Set doc = coll.GetFirstDocument
         While Not doc Is Nothing
            If Not Iselement(uniqueUNID(doc.UniversalID)) Then
               x = Ubound(restore)+1
               Redim Preserve restore(x)
               Set restore(x) = doc
               uniqueUNID(doc.UniversalID) = 1
               continue = True
            End If
            Set doc = coll.GetNextDocument(doc)
         Wend

We're going to continue finding the related documents until nothing more is added to our array. That's what the "continue" variable does. Each time, go through every document in our array. Find all the children of this document by looking up its UNID in the view. (Remember that the view is set up to show $Ref, so this search finds children of the current document in the array). Put all the children into the array if they haven't already been put in there (they might have been initially selected by the administrator). If a document is added to the array, then set "continue" back to true so we'll continue processing.

         If restore(i).HasItem("$Ref") Then
            Set coll = view.GetAllDocumentsByKey(restore(i).GetItemValue("$Ref")(0), True)
            Set doc = coll.GetFirstDocument
            While Not doc Is Nothing
               If Not Iselement(uniqueUNID(doc.UniversalID)) Then
                  x = Ubound(restore)+1
                  Redim Preserve restore(x)
                  Set restore(x) = doc
                  uniqueUNID(doc.UniversalID) = 1
                  continue = True
               End If
               Set doc = coll.GetNextDocument(doc)
            Wend
         End If
      Next
   Wend

If the document is a response document, then try to find its parent in the view. If this document's parent is found and isn't already in the array, then put it into the array.

   For i = 0 To Ubound(restore)
      temp = Evaluate("@UndeleteDocument", restore(i))
   Next
End Sub

Finally, go through all the documents in the array and run the @UndeleteDocument function on the document, which will restore it.

That should do it. Now, when the administrator chooses a document to restore, all related documents (that are in the deleted status) will also be restored, so you're assured of leaving no deletion stubs on either the production side or the deleted documents side. Obviously, you'll want to make sure you're administrators know this is how the restore works, or else you might freak them out when 15 documents are restored when they only selected one.