/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.web.bean.wcm; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import javax.faces.context.FacesContext; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.alfresco.web.app.AlfrescoNavigationHandler; import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.wizard.BaseWizardBean; import org.alfresco.web.bean.wizard.InviteUsersWizard.UserGroupRole; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author Kevin Roast */ public class CreateWebsiteWizard extends BaseWizardBean { private static final String MSG_DESCRIPTION = "description"; private static final String MSG_NAME = "name"; private static final String MSG_USERROLES = "create_website_summary_users"; private static final String ROLE_CONTENT_MANAGER = "ContentManager"; private static Log logger = LogFactory.getLog(CreateWebsiteWizard.class); protected String name; protected String title; protected String description; private String websitesFolderId = null; protected AVMService avmService; protected PermissionService permissionService; // ------------------------------------------------------------------------------ // Wizard implementation /** * Initialises the wizard */ public void init(Map parameters) { super.init(parameters); this.name = null; this.title = null; this.description = null; // init the dependant bean we are using for the invite users pages InviteWebsiteUsersWizard wiz = getInviteUsersWizard(); wiz.init(); } /** * @see org.alfresco.web.bean.dialog.BaseDialogBean#finishImpl(javax.faces.context.FacesContext, java.lang.String) */ protected String finishImpl(FacesContext context, String outcome) throws Exception { // create the website space in the correct parent folder String websiteParentId = getWebsitesFolderId(); FileInfo fileInfo = this.fileFolderService.create( new NodeRef(Repository.getStoreRef(), websiteParentId), this.name, ContentModel.TYPE_AVMWEBFOLDER); NodeRef nodeRef = fileInfo.getNodeRef(); if (logger.isDebugEnabled()) logger.debug("Created website folder node with name: " + this.name); // apply the uifacets aspect - icon, title and description props Map uiFacetsProps = new HashMap(4); uiFacetsProps.put(ContentModel.PROP_ICON, AVMConstants.SPACE_ICON_WEBSITE); uiFacetsProps.put(ContentModel.PROP_TITLE, this.title); uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, this.description); this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_UIFACETS, uiFacetsProps); InviteWebsiteUsersWizard wiz = getInviteUsersWizard(); wiz.setNode(new Node(nodeRef)); outcome = wiz.finish(); if (outcome != null) { // create a sandbox for each user appropriately with permissions based on role // build a list of managers who will have full permissions on ALL staging areas List managers = new ArrayList(4); boolean foundCurrentUser = false; List invitedUserRoles = (List)wiz.getUserRolesDataModel().getWrappedData(); String currentUser = Application.getCurrentUser(context).getUserName(); for (UserGroupRole userRole : invitedUserRoles) { String authority = userRole.getAuthority(); if (currentUser.equals(authority)) { foundCurrentUser = true; } if (ROLE_CONTENT_MANAGER.equals(userRole)) { managers.add(authority); } } if (foundCurrentUser == false) { invitedUserRoles.add(new UserGroupRole(currentUser, ROLE_CONTENT_MANAGER, null)); managers.add(currentUser); } // build the sandboxes now we have the manager list and complete user list for (UserGroupRole userRole : invitedUserRoles) { createUserSandbox(this.name, managers, userRole.getAuthority(), userRole.getRole()); } // create the AVM stores to represent the newly created location website createStagingSandbox(this.name, managers); // save the list of invited users against the store for (UserGroupRole userRole : invitedUserRoles) { // create an app:webuser instance for each authority and assoc to the website node Map props = new HashMap(2, 1.0f); props.put(ContentModel.PROP_WEBUSERNAME, userRole.getAuthority()); props.put(ContentModel.PROP_WEBUSERROLE, userRole.getRole()); this.nodeService.createNode(nodeRef, ContentModel.ASSOC_WEBUSER, ContentModel.ASSOC_WEBUSER, ContentModel.TYPE_WEBUSER, props); } // set the property on the node to reference the AVM store this.nodeService.setProperty(nodeRef, ContentModel.PROP_AVMSTORE, this.name); // navigate to the Websites folder so we can see the newly created folder this.navigator.setCurrentNodeId(websiteParentId); outcome = AlfrescoNavigationHandler.CLOSE_WIZARD_OUTCOME; } return outcome; } /** * @param avmService The AVMService to set. */ public void setAvmService(AVMService avmService) { this.avmService = avmService; } /** * @param permissionService The permissionService to set. */ public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } /** * @return Returns the name. */ public String getName() { return name; } /** * @param name The name to set. */ public void setName(String name) { this.name = name; } /** * @return Returns the title. */ public String getTitle() { return title; } /** * @param title The title to set. */ public void setTitle(String title) { this.title = title; } /** * @return Returns the description. */ public String getDescription() { return description; } /** * @param description The description to set. */ public void setDescription(String description) { this.description = description; } /** * @return summary text for the wizard */ public String getSummary() { FacesContext fc = FacesContext.getCurrentInstance(); ResourceBundle bundle = Application.getBundle(fc); // build a summary section to list the invited users and there roles StringBuilder buf = new StringBuilder(128); List invitedUserRoles = (List)getInviteUsersWizard().getUserRolesDataModel().getWrappedData(); String currentUser = Application.getCurrentUser(fc).getUserName(); boolean foundCurrentUser = false; for (UserGroupRole userRole : invitedUserRoles) { if (currentUser.equals(userRole.getAuthority())) { foundCurrentUser = true; } buf.append(userRole.getLabel()); buf.append("
"); } if (foundCurrentUser == false) { buf.append(getInviteUsersWizard().buildLabelForUserAuthorityRole(currentUser, ROLE_CONTENT_MANAGER)); } return buildSummary( new String[] {bundle.getString(MSG_NAME), bundle.getString(MSG_DESCRIPTION), bundle.getString(MSG_USERROLES)}, new String[] {this.name, this.description, buf.toString()}); } /** * @return the InviteWebsiteUsersWizard delegate bean */ private InviteWebsiteUsersWizard getInviteUsersWizard() { return (InviteWebsiteUsersWizard)FacesHelper.getManagedBean( FacesContext.getCurrentInstance(), "InviteWebsiteUsersWizard"); } /** * Helper to get the ID of the 'Websites' system folder * * @return ID of the 'Websites' system folder * * @throws AlfrescoRuntimeException if unable to find the required folder */ private String getWebsitesFolderId() { if (this.websitesFolderId == null) { // get the template from the special Content Templates folder FacesContext fc = FacesContext.getCurrentInstance(); String xpath = Application.getRootPath(fc) + "/" + Application.getWebsitesFolderName(fc); NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); NamespaceService resolver = Repository.getServiceRegistry(fc).getNamespaceService(); List results = this.searchService.selectNodes(rootNodeRef, xpath, null, resolver, false); if (results.size() == 1) { this.websitesFolderId = results.get(0).getId(); } else { throw new AlfrescoRuntimeException("Unable to find 'Websites' system folder at: " + xpath); } } return this.websitesFolderId; } /** * Create the staging sandbox for the named store. * * A staging sandbox is comprised of two stores, the first named 'storename-staging' with a * preview store named 'storename-preview' layered over the staging store. * * Various store meta-data properties are set including: * Identifier for store-types: .sandbox.staging.main and .sandbox.staging.preview * Store-id: .sandbox-id. (unique across all stores in the sandbox) * DNS: .dns. = * Website Name: .website.name = website name * * @param name The store name to create the sandbox for * @param managers The list of authorities who have ContentManager role in the website */ private void createStagingSandbox(String name, List managers) { // create the 'staging' store for the website String stagingStore = AVMConstants.buildAVMStagingStoreName(name); this.avmService.createAVMStore(stagingStore); if (logger.isDebugEnabled()) logger.debug("Created staging sandbox store: " + stagingStore); // create the system directories 'appBase' and 'avm_webapps' String path = stagingStore + ":/"; //this.fileFolderService.create(AVMNodeConverter.ToNodeRef(-1, path), AVMConstants.DIR_APPBASE, ContentModel.TYPE_AVM_PLAIN_FOLDER); this.avmService.createDirectory(path, AVMConstants.DIR_APPBASE); NodeRef dirRef = AVMNodeConverter.ToNodeRef(-1, path + '/' + AVMConstants.DIR_APPBASE); for (String manager : managers) { this.permissionService.setPermission(dirRef, manager, ROLE_CONTENT_MANAGER, true); } path += AVMConstants.DIR_APPBASE; //this.fileFolderService.create(AVMNodeConverter.ToNodeRef(-1, path), AVMConstants.DIR_WEBAPPS, ContentModel.TYPE_AVM_PLAIN_FOLDER); this.avmService.createDirectory(path, AVMConstants.DIR_WEBAPPS); // tag the store with the store type this.avmService.setStoreProperty(stagingStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_STAGING_MAIN), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with the DNS name property tagStoreDNSPath(stagingStore, name, "staging"); // create the 'preview' store for the website String previewStore = AVMConstants.buildAVMStagingPreviewStoreName(name); this.avmService.createAVMStore(previewStore); if (logger.isDebugEnabled()) logger.debug("Created staging sandbox store: " + previewStore); // create a layered directory pointing to 'appBase' in the staging area path = previewStore + ":/"; String targetPath = name + AVMConstants.STORE_STAGING + ":/" + AVMConstants.DIR_APPBASE; //this.fileFolderService.create(AVMNodeConverter.ToNodeRef(-1, path), AVMConstants.DIR_APPBASE, ContentModel.TYPE_AVM_PLAIN_FOLDER); this.avmService.createLayeredDirectory(targetPath, path, AVMConstants.DIR_APPBASE); dirRef = AVMNodeConverter.ToNodeRef(-1, path + '/' + AVMConstants.DIR_APPBASE); for (String manager : managers) { this.permissionService.setPermission(dirRef, manager, ROLE_CONTENT_MANAGER, true); } // tag the store with the store type this.avmService.setStoreProperty(previewStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_STAGING_PREVIEW), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with the DNS name property tagStoreDNSPath(previewStore, name, "preview"); // tag all related stores to indicate that they are part of a single sandbox String sandboxIdProp = AVMConstants.PROP_SANDBOXID + GUID.generate(); this.avmService.setStoreProperty(stagingStore, QName.createQName(null, sandboxIdProp), new PropertyValue(DataTypeDefinition.TEXT, null)); this.avmService.setStoreProperty(previewStore, QName.createQName(null, sandboxIdProp), new PropertyValue(DataTypeDefinition.TEXT, null)); if (logger.isDebugEnabled()) { dumpStoreProperties(stagingStore); dumpStoreProperties(previewStore); } } /** * Create a user sandbox for the named store. * * A user sandbox is comprised of two stores, the first named 'storename-username-main' layered * over the staging store with a preview store named 'storename-username-preview' layered over * the main store. * * Various store meta-data properties are set including: * Identifier for store-types: .sandbox.author.main and .sandbox.author.preview * Store-id: .sandbox-id. (unique across all stores in the sandbox) * DNS: .dns. = * Website Name: .website.name = website name * * @param name The store name to create the sandbox for * @param managers The list of authorities who have ContentManager role in the website * @param username Username of the user to create the sandbox for * @param role Role permission for the user */ private void createUserSandbox(String name, List managers, String username, String role) { // create the user 'main' store String userStore = AVMConstants.buildAVMUserMainStoreName(name, username); this.avmService.createAVMStore(userStore); if (logger.isDebugEnabled()) logger.debug("Created staging sandbox store: " + userStore); // create a layered directory pointing to 'appBase' in the staging area String path = userStore + ":/"; String targetPath = name + AVMConstants.STORE_STAGING + ":/" + AVMConstants.DIR_APPBASE; this.avmService.createLayeredDirectory(targetPath, path, AVMConstants.DIR_APPBASE); NodeRef dirRef = AVMNodeConverter.ToNodeRef(-1, path + '/' + AVMConstants.DIR_APPBASE); this.permissionService.setPermission(dirRef, username, role, true); for (String manager : managers) { this.permissionService.setPermission(dirRef, manager, ROLE_CONTENT_MANAGER, true); } // tag the store with the store type this.avmService.setStoreProperty(userStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_AUTHOR_MAIN), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with the base name of the website so that corresponding // staging areas can be found. this.avmService.setStoreProperty(userStore, QName.createQName(null, AVMConstants.PROP_WEBSITE_NAME), new PropertyValue(DataTypeDefinition.TEXT, name)); // tag the store, oddly enough, with its own store name for querying. // when will the madness end. this.avmService.setStoreProperty(userStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_STORE_PREFIX + userStore), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with the DNS name property tagStoreDNSPath(userStore, name, username); // create the user 'preview' store String previewStore = AVMConstants.buildAVMUserPreviewStoreName(name, username); this.avmService.createAVMStore(previewStore); if (logger.isDebugEnabled()) logger.debug("Created staging sandbox store: " + previewStore); // create a layered directory pointing to 'appBase' in the user 'main' store path = previewStore + ":/"; targetPath = userStore + ":/" + AVMConstants.DIR_APPBASE; this.avmService.createLayeredDirectory(targetPath, path, AVMConstants.DIR_APPBASE); dirRef = AVMNodeConverter.ToNodeRef(-1, path + '/' + AVMConstants.DIR_APPBASE); this.permissionService.setPermission(dirRef, username, role, true); for (String manager : managers) { this.permissionService.setPermission(dirRef, manager, ROLE_CONTENT_MANAGER, true); } // tag the store with the store type this.avmService.setStoreProperty(previewStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_AUTHOR_PREVIEW), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with its own store name for querying. this.avmService.setStoreProperty(previewStore, QName.createQName(null, AVMConstants.PROP_SANDBOX_STORE_PREFIX + previewStore), new PropertyValue(DataTypeDefinition.TEXT, null)); // tag the store with the DNS name property tagStoreDNSPath(previewStore, name, username, "preview"); // tag all related stores to indicate that they are part of a single sandbox String sandboxIdProp = AVMConstants.PROP_SANDBOXID + GUID.generate(); this.avmService.setStoreProperty(userStore, QName.createQName(null, sandboxIdProp), new PropertyValue(DataTypeDefinition.TEXT, null)); this.avmService.setStoreProperty(previewStore, QName.createQName(null, sandboxIdProp), new PropertyValue(DataTypeDefinition.TEXT, null)); if (logger.isDebugEnabled()) { dumpStoreProperties(userStore); dumpStoreProperties(previewStore); } } /** * Tag a named store with a DNS path meta-data attribute. * The DNS meta-data attribute is set to the system path 'store:/appBase/avm_webapps' * * @param store Name of the store to tag */ private void tagStoreDNSPath(String store, String... components) { String path = store + ":/" + AVMConstants.DIR_APPBASE + '/' + AVMConstants.DIR_WEBAPPS; // DNS name mangle the property name - can only contain value DNS characters! String dnsProp = AVMConstants.PROP_DNS + DNSNameMangler.MakeDNSName(components); this.avmService.setStoreProperty(store, QName.createQName(null, dnsProp), new PropertyValue(DataTypeDefinition.TEXT, path)); } /** * Debug helper method to dump the properties of a store * * @param store Store name to dump properties for */ private void dumpStoreProperties(String store) { Map props = avmService.getStoreProperties(store); for (QName name : props.keySet()) { logger.debug(" " + name + ": " + props.get(name)); } } }