diff --git a/source/java/org/alfresco/web/bean/wcm/AVMConstants.java b/source/java/org/alfresco/web/bean/wcm/AVMConstants.java index 9da1dedf54..93fe9a316d 100644 --- a/source/java/org/alfresco/web/bean/wcm/AVMConstants.java +++ b/source/java/org/alfresco/web/bean/wcm/AVMConstants.java @@ -737,17 +737,18 @@ public final class AVMConstants public final static String DIR_ROOT = "ROOT"; // system property keys for sandbox identification and DNS virtualisation mapping - public final static String PROP_SANDBOXID = ".sandbox-id."; - public final static String PROP_DNS = ".dns."; - public final static String PROP_SANDBOX_STORE_PREFIX = ".sandbox.store."; - public final static QName PROP_WEB_PROJECT_NODE_REF = QName.createQName(null, ".web_project.noderef"); - public final static QName PROP_SANDBOX_STAGING_MAIN = QName.createQName(null, ".sandbox.staging.main"); - public final static QName PROP_SANDBOX_STAGING_PREVIEW = QName.createQName(null, ".sandbox.staging.preview"); - public final static QName PROP_SANDBOX_AUTHOR_MAIN = QName.createQName(null, ".sandbox.author.main"); - public final static QName PROP_SANDBOX_AUTHOR_PREVIEW = QName.createQName(null, ".sandbox.author.preview"); - public final static QName PROP_SANDBOX_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.workflow.main"); + public final static String PROP_BACKGROUND_LAYER = ".background-layer."; + public final static String PROP_SANDBOXID = ".sandbox-id."; + public final static String PROP_DNS = ".dns."; + public final static String PROP_SANDBOX_STORE_PREFIX = ".sandbox.store."; + public final static QName PROP_WEB_PROJECT_NODE_REF = QName.createQName(null, ".web_project.noderef"); + public final static QName PROP_SANDBOX_STAGING_MAIN = QName.createQName(null, ".sandbox.staging.main"); + public final static QName PROP_SANDBOX_STAGING_PREVIEW = QName.createQName(null, ".sandbox.staging.preview"); + public final static QName PROP_SANDBOX_AUTHOR_MAIN = QName.createQName(null, ".sandbox.author.main"); + public final static QName PROP_SANDBOX_AUTHOR_PREVIEW = QName.createQName(null, ".sandbox.author.preview"); + public final static QName PROP_SANDBOX_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.workflow.main"); public final static QName PROP_SANDBOX_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.workflow.preview"); - public final static QName PROP_WEBSITE_NAME = QName.createQName(null, ".website.name"); + public final static QName PROP_WEBSITE_NAME = QName.createQName(null, ".website.name"); public final static String SPACE_ICON_WEBSITE = "space-icon-website"; diff --git a/source/java/org/alfresco/web/bean/wcm/AddAvmContentDialog.java b/source/java/org/alfresco/web/bean/wcm/AddAvmContentDialog.java index 1b92542b1c..fa478e4681 100644 --- a/source/java/org/alfresco/web/bean/wcm/AddAvmContentDialog.java +++ b/source/java/org/alfresco/web/bean/wcm/AddAvmContentDialog.java @@ -44,6 +44,9 @@ public class AddAvmContentDialog extends AddContentDialog /** AVM Browse Bean reference */ protected AVMBrowseBean avmBrowseBean; + + /** */ + protected String path; /** @@ -75,11 +78,11 @@ public class AddAvmContentDialog extends AddContentDialog // create the file this.avmService.createFile(parent, this.fileName); - String path = parent + '/' + this.fileName; - NodeRef fileNodeRef = AVMNodeConverter.ToNodeRef(-1, path); + this.path = parent + '/' + this.fileName; + NodeRef fileNodeRef = AVMNodeConverter.ToNodeRef(-1, this.path); if (logger.isDebugEnabled()) - logger.debug("Created AVM file: " + path); + logger.debug("Created AVM file: " + this.path); // apply the titled aspect - title and description Map titledProps = new HashMap(2, 1.0f); @@ -100,11 +103,6 @@ public class AddAvmContentDialog extends AddContentDialog writer.putContent(strContent == null ? "" : strContent); } - // reload the virtualisation server as required - if (logger.isDebugEnabled()) - logger.debug("Reloading virtualisation server on path: " + path); - AVMConstants.updateVServerWebapp(path, false); - // remember the created node now this.createdNode = fileNodeRef; } @@ -116,6 +114,22 @@ public class AddAvmContentDialog extends AddContentDialog protected String doPostCommitProcessing(FacesContext context, String outcome) { clearUpload(); + + // Notify virtualization server + // + // This must be done in doPostCommitProcessing so that the notification + // can only be received by the virtualization server *after* the content + // update transaction within the AVM has completed. Otherwise, there's + // a race condition that can cause the virtualization server to not be + // able to read the new (or modified) web.xml file within the virtual + // webapps being relaoded via the call to updateVServerWebapp. + + if (logger.isDebugEnabled()) + { + logger.debug("Reloading virtualisation server on path: " + this.path); + } + + AVMConstants.updateVServerWebapp(this.path, false); return outcome; } diff --git a/source/java/org/alfresco/web/bean/wcm/DeleteSandboxDialog.java b/source/java/org/alfresco/web/bean/wcm/DeleteSandboxDialog.java index 3993cf3a1d..7745141534 100644 --- a/source/java/org/alfresco/web/bean/wcm/DeleteSandboxDialog.java +++ b/source/java/org/alfresco/web/bean/wcm/DeleteSandboxDialog.java @@ -74,12 +74,36 @@ public class DeleteSandboxDialog extends BaseDialogBean { // found the sandbox to remove String storeRoot = (String)website.getProperties().get(WCMAppModel.PROP_AVMSTORE); + String sandbox = AVMConstants.buildUserMainStoreName(storeRoot, username); + String path = AVMConstants.buildStoreWebappPath(sandbox, this.avmBrowseBean.getWebapp()); + + // Notifiy virtualisation server about removing this sandbox. + // + // Implementation note: + // + // Because the removal of virtual webapps in the + // virtualization server is recursive, it only + // needs to be given the name of the main store. + // + // This notification must occur *prior* to purging content + // within the AVM because the virtualization server must list + // the avm_webapps dir in each store to discover which + // virtual webapps must be unloaded. The virtualization + // server traverses the sandbox's stores in most-to-least + // dependent order, so clients don't have to worry about + // accessing a preview layer whose main layer has been torn + // out from under it. + + AVMConstants.removeVServerWebapp(path, true); + - // TODO: would it be better to use the .sandbox-id. property to delete all sandboxes? - + // TODO: Use the .sandbox-id. property to delete all sandboxes, + // rather than assume a sandbox always had a single preview + // layer attached. + // purge the user main sandbox store from the system - String sandbox = AVMConstants.buildUserMainStoreName(storeRoot, username); this.avmService.purgeStore(sandbox); + // purge the user preview sandbox store from the system sandbox = AVMConstants.buildUserPreviewStoreName(storeRoot, username); @@ -88,9 +112,6 @@ public class DeleteSandboxDialog extends BaseDialogBean // remove the association to this web project user meta-data this.nodeService.removeChild(website.getNodeRef(), ref.getChildRef()); - // update virtualisation server for the sandbox removal - String path = AVMConstants.buildStoreWebappPath(sandbox, this.avmBrowseBean.getWebapp()); - AVMConstants.removeVServerWebapp(path, true); break; } diff --git a/source/java/org/alfresco/web/bean/wcm/ImportWebsiteDialog.java b/source/java/org/alfresco/web/bean/wcm/ImportWebsiteDialog.java index d58ee2ae40..a0b7f7a375 100644 --- a/source/java/org/alfresco/web/bean/wcm/ImportWebsiteDialog.java +++ b/source/java/org/alfresco/web/bean/wcm/ImportWebsiteDialog.java @@ -207,11 +207,11 @@ public class ImportWebsiteDialog AVMConstants.getStoreName(rootPath), "Import of file: " + this.fileName, null); + tx.commit(); + // Reload virtualisation server as required AVMConstants.updateVServerWebapp(rootPath, true); - tx.commit(); - UIContextService.getInstance(context).notifyBeans(); outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; diff --git a/source/java/org/alfresco/web/bean/wcm/InviteWebsiteUsersWizard.java b/source/java/org/alfresco/web/bean/wcm/InviteWebsiteUsersWizard.java index a7090568f4..42dfc2bcf3 100644 --- a/source/java/org/alfresco/web/bean/wcm/InviteWebsiteUsersWizard.java +++ b/source/java/org/alfresco/web/bean/wcm/InviteWebsiteUsersWizard.java @@ -62,6 +62,9 @@ public class InviteWebsiteUsersWizard extends InviteUsersWizard /** AVM Browse Bean reference */ protected AVMBrowseBean avmBrowseBean; + + /** Data for virtualization server notification */ + private List sandboxInfoList; /** @@ -163,7 +166,8 @@ public class InviteWebsiteUsersWizard extends InviteUsersWizard // build the sandboxes now we have the manager list and complete user list // and create an association to a node to represent each invited user - List sandboxInfoList = new LinkedList(); + this.sandboxInfoList = new LinkedList(); + for (UserGroupRole userRole : this.userGroupRoles) { for (String userAuth : findNestedUserAuthorities(userRole.getAuthority())) @@ -188,19 +192,36 @@ public class InviteWebsiteUsersWizard extends InviteUsersWizard } } - // reload virtualisation server for webapp in this web project - if (isStandalone()) - { - for (SandboxInfo sandboxInfo : sandboxInfoList) - { - String newlyInvitedStoreName = AVMConstants.buildStagingStoreName(sandboxInfo.getMainStoreName()); - String path = AVMConstants.buildStoreWebappPath(newlyInvitedStoreName, this.avmBrowseBean.getWebapp()); - AVMConstants.updateVServerWebapp(path, true); - } - } - return outcome; } + + /** + * Handle notification to the virtualization server + * (this needs to occur after the sandbox is created). + */ + @Override + protected String doPostCommitProcessing(FacesContext context, String outcome) + { + // reload virtualisation server for webapp in this web project + if (isStandalone()) + { + for (SandboxInfo sandboxInfo : this.sandboxInfoList) + { + String newlyInvitedStoreName = + AVMConstants.buildStagingStoreName( + sandboxInfo.getMainStoreName()); + + String path = + AVMConstants.buildStoreWebappPath( + newlyInvitedStoreName, this.avmBrowseBean.getWebapp()); + + AVMConstants.updateVServerWebapp(path, true); + } + } + return outcome; + } + + /** * Find all nested user authorities contained with an authority diff --git a/source/java/org/alfresco/web/bean/wcm/SandboxFactory.java b/source/java/org/alfresco/web/bean/wcm/SandboxFactory.java index 7e6ed69dee..62e756fab3 100644 --- a/source/java/org/alfresco/web/bean/wcm/SandboxFactory.java +++ b/source/java/org/alfresco/web/bean/wcm/SandboxFactory.java @@ -125,6 +125,9 @@ public final class SandboxFactory // tag the store with the DNS name property tagStoreDNSPath(avmService, previewStoreName, storeId, "preview"); + + // The preview store depends on the main staging store (dist=1) + tagStoreBackgroundLayer(avmService,previewStoreName,stagingStoreName,1); // snapshot the store avmService.createSnapshot(previewStoreName, null, null); @@ -221,7 +224,6 @@ public final class SandboxFactory new PropertyValue(DataTypeDefinition.TEXT, storeId)); // tag the store, oddly enough, with its own store name for querying. - // when will the madness end. avmService.setStoreProperty(userStoreName, QName.createQName(null, AVMConstants.PROP_SANDBOX_STORE_PREFIX + userStoreName), new PropertyValue(DataTypeDefinition.TEXT, null)); @@ -229,10 +231,12 @@ public final class SandboxFactory // tag the store with the DNS name property tagStoreDNSPath(avmService, userStoreName, storeId, username); + // The user store depends on the main staging store (dist=1) + tagStoreBackgroundLayer(avmService,userStoreName,stagingStoreName,1); + // snapshot the store avmService.createSnapshot(userStoreName, null, null); - // create the user 'preview' store avmService.createStore(previewStoreName); if (logger.isDebugEnabled()) @@ -266,6 +270,13 @@ public final class SandboxFactory // tag the store with the DNS name property tagStoreDNSPath(avmService, previewStoreName, storeId, username, "preview"); + // The preview user store depends on the main user store (dist=1) + tagStoreBackgroundLayer(avmService,previewStoreName, userStoreName,1); + + // The preview user store depends on the main staging store (dist=2) + tagStoreBackgroundLayer(avmService,previewStoreName, stagingStoreName,2); + + // snapshot the store avmService.createSnapshot(previewStoreName, null, null); @@ -335,7 +346,6 @@ public final class SandboxFactory new PropertyValue(DataTypeDefinition.TEXT, storeId)); // tag the store, oddly enough, with its own store name for querying. - // when will the madness end. avmService.setStoreProperty(mainStoreName, QName.createQName(null, AVMConstants.PROP_SANDBOX_STORE_PREFIX + mainStoreName), new PropertyValue(DataTypeDefinition.TEXT, null)); @@ -343,6 +353,10 @@ public final class SandboxFactory // tag the store with the DNS name property tagStoreDNSPath(avmService, mainStoreName, storeId, packageName); + + // The main workflow store depends on the main staging store (dist=1) + tagStoreBackgroundLayer(avmService,mainStoreName, stagingStoreName ,1); + // snapshot the store avmService.createSnapshot(mainStoreName, null, null); @@ -371,6 +385,14 @@ public final class SandboxFactory // tag the store with the DNS name property tagStoreDNSPath(avmService, previewStoreName, storeId, packageName, "preview"); + + + // The preview worfkflow store depends on the main workflow store (dist=1) + tagStoreBackgroundLayer(avmService,previewStoreName, mainStoreName,1); + + // The preview user store depends on the main staging store (dist=2) + tagStoreBackgroundLayer(avmService,previewStoreName, stagingStoreName,2); + // snapshot the store avmService.createSnapshot(previewStoreName, null, null); @@ -407,6 +429,43 @@ public final class SandboxFactory avmService.setStoreProperty(store, QName.createQName(null, dnsProp), new PropertyValue(DataTypeDefinition.TEXT, path)); } + + /** + * Tags a store with a property that indicates one of its + * backgroundStore layers, and the distance of that layer. + * This function must be called separately for each background + * store; for example the "mysite--alice--preview" store had + * as its immediate background "mysite--alice", which itself had + * as its background store "mysite", you'd make a sequence of + * calls like this: + * + *
+    *    tagStoreBackgroundLayer("mysite--alice",          "mysite",        1);
+    *    tagStoreBackgroundLayer("mysite--alice--preview", "mysite--alice", 1);
+    *    tagStoreBackgroundLayer("mysite--alice--preview", "mysite",        2);
+    *   
+ * + * This make it easy for other parts of the system to determine + * which stores depend on others directly or indirectly (which is + * useful for reloading virtualized webapps). + * + * @param store Name of the store to tag + * @param backgroundStore Name of store's background store + * @param distance Distance from store. + * The backgroundStore 'mysite' is 1 away from the store 'mysite--alice' + * but 2 away from the store 'mysite--alice--preview'. + */ + private static void tagStoreBackgroundLayer(AVMService avmService, + String store, + String backgroundStore, + int distance) + { + String prop_key = AVMConstants.PROP_BACKGROUND_LAYER + backgroundStore; + avmService.setStoreProperty(store, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, distance)); + } + + /** * Debug helper method to dump the properties of a store diff --git a/source/java/org/alfresco/web/forms/ServletContextFormDataFunctionsAdapter.java b/source/java/org/alfresco/web/forms/ServletContextFormDataFunctionsAdapter.java index 0a01f4a092..f085809a09 100644 --- a/source/java/org/alfresco/web/forms/ServletContextFormDataFunctionsAdapter.java +++ b/source/java/org/alfresco/web/forms/ServletContextFormDataFunctionsAdapter.java @@ -38,17 +38,16 @@ public class ServletContextFormDataFunctionsAdapter private String toAVMPath(String path) { - // The real_path will look something like this: - // /alfresco.avm/avm.alfresco.localhost/$-1$alfreco-guest-main:/www/avm_webapps/my_webapp - System.err.println("looking up real path for " + path); + // The "real path" will look something like: + // /media/alfresco/cifs/v/mysite--bob/VERSION/v-1/DATA/www/avm_webapps/ROOT/media/releases/content + path = this.servletContext.getRealPath(path); - System.err.println("got real path " + path); try { - final JNDIPath jndiPath = new JNDIPath(AVMFileDirContext.getAVMFileDirAppBase(), path); - // The avm_path to the root of the context will look something like this: - // alfreco-guest-main:/www/avm_webapps/my_webapp - + // The corresponding AVM path will look something like: + // mysite--bob:/www/avm_webapps/ROOT/media/releases/content + + final JNDIPath jndiPath = new JNDIPath(AVMFileDirContext.getAVMFileDirMountPoint(), path); return jndiPath.getAvmPath(); } catch (Exception e)