/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.portal.mop.management.operations;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.exoplatform.commons.chromattic.ChromatticManager;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.model.NavigationFragment;
import org.exoplatform.portal.config.model.Page;
import org.exoplatform.portal.config.model.PageNavigation;
import org.exoplatform.portal.config.model.PageNode;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.description.DescriptionService;
import org.exoplatform.portal.mop.importer.ImportMode;
import org.exoplatform.portal.mop.management.exportimport.NavigationExportTask;
import org.exoplatform.portal.mop.management.exportimport.NavigationImportTask;
import org.exoplatform.portal.mop.management.exportimport.PageExportTask;
import org.exoplatform.portal.mop.management.exportimport.PageImportTask;
import org.exoplatform.portal.mop.management.exportimport.SiteLayoutExportTask;
import org.exoplatform.portal.mop.management.exportimport.SiteLayoutImportTask;
import org.exoplatform.portal.mop.management.operations.navigation.NavigationUtils;
import org.exoplatform.portal.mop.management.operations.page.PageUtils;
import org.exoplatform.portal.mop.navigation.NavigationContext;
import org.exoplatform.portal.mop.navigation.NavigationService;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.pom.config.POMSession;
import org.exoplatform.portal.pom.config.POMSessionManager;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.Query;
import org.exoplatform.services.organization.User;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.management.api.ContentType;
import org.gatein.management.api.binding.Marshaller;
import org.gatein.management.api.exceptions.OperationException;
import org.gatein.management.api.exceptions.ResourceNotFoundException;
import org.gatein.management.api.operation.OperationAttachment;
import org.gatein.management.api.operation.OperationContext;
import org.gatein.management.api.operation.OperationHandler;
import org.gatein.management.api.operation.ResultHandler;
import org.gatein.management.api.operation.model.NoResultModel;
import org.gatein.mop.api.workspace.Site;
import org.gatein.mop.api.workspace.Workspace;
/**
* @author <a href="mailto:lponce@redhat.com">Lucas Ponce</a>
* @version $Revision$
*/
public class TemplateImportResource extends SecureOperationHandler implements OperationHandler {
private static final Logger log = LoggerFactory.getLogger(TemplateImportResource.class);
private static final Set<String> FILES;
private static final Set<String> DIR;
static {
HashSet<String> files = new HashSet<String>(5);
files.add("portal.xml");
files.add("group.xml");
files.add("user.xml");
files.add("pages.xml");
files.add("navigation.xml");
FILES = files;
HashSet<String> dir = new HashSet<String>(3);
dir.add("portal");
dir.add("group");
dir.add("user");
DIR = dir;
}
private final String OWNER = "@owner@";
private final String CREATE = "create";
@Override
public void doExecute(final OperationContext operationContext, ResultHandler resultHandler)
throws ResourceNotFoundException, OperationException {
final String PORTAL = "portal";
final String GROUP = "group";
final String USER = "user";
OperationAttributes attr = new OperationAttributes();
initAttributes(operationContext, attr);
List<MopTemplate> templates = readZip(operationContext, attr);
BackendServices svc = new BackendServices();
initBackendServices(operationContext, attr, svc);
// Expands template with proper sites/groups/users and populates importMap to perform import operation
Map<SiteKey, MopImport> importMap = null;
if (PORTAL.equals(attr.importType)) {
importMap = expandPortalTemplate(attr, svc, templates);
} else if (GROUP.equals(attr.importType)) {
importMap = expandGroupTemplate(attr, svc, templates);
} else if (USER.equals(attr.importType)) {
importMap = expandUserTemplate(attr, svc, templates);
}
validationRules(attr, importMap);
OperationException importError = performImport(attr, importMap);
endRequest(attr, svc, importError);
resultHandler.completed(NoResultModel.INSTANCE);
}
private void initAttributes(OperationContext operationContext, OperationAttributes attr)
throws OperationException {
// Expected operationName == "import-resource"
attr.operationName = operationContext.getOperationName();
// Expected importType == {template-type: portal|group|user}
attr.importType = operationContext.getAddress().resolvePathTemplate("template-type");
// Expected mode == {merge (default), overwrite, conserve, insert}
attr.mode = operationContext.getAttributes().getValue("importMode");
if (attr.mode == null || "".equals(attr.mode))
attr.mode = "merge";
try {
attr.importMode = ImportMode.valueOf(attr.mode.trim().toUpperCase());
} catch (Exception e) {
throw new OperationException(attr.operationName,
"Unknown importMode " + attr.mode + " for " + attr.importType + " template import.");
}
// Expression for users. This option is valid when importType == user
attr.targetExpr = operationContext.getAttributes().getValue("targetExpr");
// List of users. Option valid when importType == user
attr.targetUser = operationContext.getAttributes().getValues("targetUser");
// Flag to create dashboard if user has not initialized it. Option valid when importType == user
// dashboardMode == create means dashboard will be created if it doesn't exist on import
attr.dashboardMode = operationContext.getAttributes().getValue("dashboardMode");
// List of groups. Option valid when importType == group
attr.targetGroup = operationContext.getAttributes().getValues("targetGroup");
// List of sites. Option valid when importType == portal
attr.targetSite = operationContext.getAttributes().getValues("targetSite");
}
private void initBackendServices(OperationContext operationContext, OperationAttributes attr, BackendServices svc)
throws OperationException {
svc.mgr = operationContext.getRuntimeContext().getRuntimeComponent(POMSessionManager.class);
POMSession session = svc.mgr.getSession();
if (session == null)
throw new OperationException(attr.operationName, "MOP session was null");
svc.workspace = session.getWorkspace();
if (svc.workspace == null)
throw new OperationException(attr.operationName, "MOP workspace was null");
svc.dataStorage = operationContext.getRuntimeContext().getRuntimeComponent(DataStorage.class);
if (svc.dataStorage == null)
throw new OperationException(attr.operationName, "DataStorage was null");
svc.pageService = operationContext.getRuntimeContext().getRuntimeComponent(PageService.class);
if (svc.pageService == null)
throw new OperationException(attr.operationName, "PageService was null");
svc.navigationService = operationContext.getRuntimeContext().getRuntimeComponent(NavigationService.class);
if (svc.navigationService == null)
throw new OperationException(attr.operationName, "Navigation service was null");
svc.descriptionService = operationContext.getRuntimeContext().getRuntimeComponent(
DescriptionService.class);
if (svc.descriptionService == null)
throw new OperationException(attr.operationName, "Description service was null");
svc.organizationService = operationContext.getRuntimeContext().getRuntimeComponent(OrganizationService.class);
if (svc.organizationService == null)
throw new OperationException(attr.operationName, "Organization service was null");
svc.chromatticManager = operationContext.getRuntimeContext().getRuntimeComponent(ChromatticManager.class);
if (svc.chromatticManager == null)
throw new OperationException(attr.operationName, "Chromattic manager was null");
}
/**
* It will have three possible layouts:
* a) portal templates:
* .zip/portal/template/
* <templateName1>/{portal,pages,navigation}.xml
* <templateName2>/{portal,pages,navigation}.xml
* ...
* <templateNameN>/{portal,pages,navigation}.xml
*
* This layout allows to have 1..N templates
*
* b) group templates:
* .zip/group/template/{portal,pages,navigation}.xml
*
* This layout only accepts 1 group template
*
* c) user templates:
* .zip/user/template/{portal,pages,navigation}.xml
*
* This layout only accepts 1 group template
*
* Each template is mapped in a MopTemplate object containing unmarshalled representation of template.
*
* @param operationContext context used in doExecute()
* @param attr attributes used for this operation
* @return list of templates read from .zip
* @throws OperationException
*/
private List<MopTemplate> readZip(OperationContext operationContext, OperationAttributes attr)
throws OperationException {
ArrayList<MopTemplate> templates = new ArrayList<MopTemplate>();
// Gets .zip attachment
OperationAttachment attachment = operationContext.getAttachment(true);
if (attachment == null)
throw new OperationException(attr.operationName,
"No attachment available for " + attr.importType + " template import.");
InputStream inputStream = attachment.getStream();
if (inputStream == null)
throw new OperationException(attr.operationName,
"No data stream available for " + attr.importType + " template import.");
// Reads .zip file
final NonCloseableZipInputStream zis = new NonCloseableZipInputStream(inputStream);
ZipEntry entry;
boolean portal = false, group = false, user = false, template = false;
try {
log.info("Preparing data for import.");
while ((entry = zis.getNextEntry()) != null) {
// Skip directories
if (entry.isDirectory()) {
// Directory entries finish with "/"
String[] folders = entry.getName().split("/");
String name = folders[folders.length - 1];
// 1st directory entry
if (!(portal || group || user)) {
if (DIR.contains(name)) {
if ("portal".equals(name)) {
portal = true;
if (!"portal".equalsIgnoreCase(attr.importType))
throw new ZipTemplateException(".zip contains a portal folder but not " +
"/template/portal operation found, " +
"instead /template/" + attr.importType + " .");
} else if ("group".equals(name)) {
group = true;
if (!"group".equalsIgnoreCase(attr.importType))
throw new ZipTemplateException(".zip contains a group folder but not " +
"/template/group operation found, " +
"instead /template/" + attr.importType + " .");
} else if ("user".equals(name)) {
user = true;
if (!"user".equalsIgnoreCase(attr.importType))
throw new ZipTemplateException(".zip contains a user folder but not " +
"/template/user operation found, " +
"instead /template/" + attr.importType + " .");
}
} else {
throw new ZipTemplateException(".zip contains a not valid folder: " + name + ". " +
"Expecting one of " + DIR);
}
} else {
// 2nd directory entry
if (!template) {
if ("template".equals(name)) {
template = true;
if (group) {
MopTemplate groupTemplate = new MopTemplate();
groupTemplate.templateType = SiteType.GROUP;
templates.add(groupTemplate);
} else if (user) {
MopTemplate userTemplate = new MopTemplate();
userTemplate.templateType = SiteType.USER;
templates.add(userTemplate);
}
} else
throw new ZipTemplateException(".zip does not contains a template folder under " +
"portal/group/user folder, instead " + name + " .");
} else {
// 3rd directory
if (portal) {
MopTemplate portalTemplate = new MopTemplate();
portalTemplate.templateName = name;
portalTemplate.templateType = SiteType.PORTAL;
templates.add(portalTemplate);
} else {
throw new ZipTemplateException(".zip contains a folder under {portal,group,user}/template/ " +
"layout. It is expected one of " + FILES);
}
}
}
continue;
}
// Parses zip entry
String[] parts = parseEntry(entry, portal);
// Expected filesystem as
// portal/template/<templateName>/{portal,pages,navigation}.xml
// group/template/{group,pages,navigation}.xml
// user/template/{user,pages.navigation}.xml
String file;
if (portal)
file = parts[3];
else
file = parts[2];
// Validates file name
if (!FILES.contains(file)) {
log.warn(".zip contains a not valid template file: " + file + ". Skipping...");
continue;
}
// Validates that a portal.xml is used with /template/portal operation
if ("portal.xml".equals(file) && !portal) {
log.warn("portal.xml in .zip but not /template/portal operation found, " +
"instead /template/" + attr.importType + " . Skipping... ");
continue;
}
// Validates that a group.xml is used with /template/group operation
if ("group.xml".equals(file) && !group) {
log.warn("group.xml in .zip but not /template/group operation found, " +
"instead /template/" + attr.importType + " . Skipping...");
continue;
}
// Validates that a user.xml is used with /template/user operation
if ("user.xml".equals(file) && !user) {
log.warn("user.xml in .zip but not /template/user operation found, " +
"instead /template/" + attr.importType + " . Skipping... ");
continue;
}
// Templates are unmarshalled in generic objects, pattern substitution will be performed later
if (SiteLayoutExportTask.FILES.contains(file)) {
Marshaller<PortalConfig> marshaller = operationContext.getBindingProvider()
.getMarshaller(PortalConfig.class, ContentType.XML);
templates.get(templates.size() - 1).portalConfig = marshaller.unmarshal(zis);
} else if (file.equals(PageExportTask.FILE)) {
Marshaller<Page.PageSet> marshaller = operationContext.getBindingProvider()
.getMarshaller(Page.PageSet.class, ContentType.XML);
templates.get(templates.size() - 1).pageSet = marshaller.unmarshal(zis);
} else if (file.equals(NavigationExportTask.FILE)) {
Marshaller<PageNavigation> marshaller = operationContext.getBindingProvider()
.getMarshaller(PageNavigation.class, ContentType.XML);
templates.get(templates.size() - 1).pageNavigation = marshaller.unmarshal(zis);
}
}
} catch (Throwable t) {
throw new OperationException(operationContext.getOperationName(), "Exception reading data for import.", t);
} finally {
try {
zis.reallyClose();
} catch (IOException e) {
log.warn("Exception closing underlying data stream from import.");
}
}
return templates;
}
private Map<SiteKey, MopImport> expandPortalTemplate(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates)
throws OperationException {
Map<SiteKey, MopImport> expanded = new HashMap<SiteKey, MopImport>();
try {
// Pairs of (portalName, MopTemplate) filtered by targetSite
Map<String, MopTemplate> portalNamesTemplates = filterPortalNames(attr, svc, templates);
for (Map.Entry<String, MopTemplate> portalNameTemplate : portalNamesTemplates.entrySet()) {
String portalName = portalNameTemplate.getKey();
MopTemplate mopTemplate = portalNameTemplate.getValue();
SiteKey newSiteKey = new SiteKey(SiteType.PORTAL, portalName);
MopImport newMopImport = new MopImport();
resolveTemplate(svc, newSiteKey, newMopImport, mopTemplate, portalName);
expanded.put(newSiteKey, newMopImport);
}
} catch (Throwable t) {
throw new OperationException("Import portal template", t.getMessage(), t);
}
return expanded;
}
private Map<String, MopTemplate> filterPortalNames(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates)
throws Exception {
Map<String, MopTemplate> portalTemplates = new HashMap<String, MopTemplate>();
List<String> portalNames = svc.dataStorage.getAllPortalNames();
// Filters portals defined in targetSite attributes
if (attr.targetSite.size() > 0) {
Iterator<String> iterPortalNames = portalNames.iterator();
while (iterPortalNames.hasNext()) {
String name = iterPortalNames.next();
boolean found = false;
for (String targetSite : attr.targetSite) {
if (targetSite.equals(name)) {
found = true;
break;
}
}
if (!found) {
iterPortalNames.remove();
}
}
}
// Attaches portal templates
for (String portalName : portalNames) {
PortalConfig pConfig = svc.dataStorage.getPortalConfig(portalName);
if (pConfig != null) {
String portalTemplate = pConfig.getProperty("template");
if (portalTemplate == null) {
log.warn("Portal " + portalName + " has not template property. " +
"It will not used in template import operation");
} else {
for (MopTemplate mopTemplate : templates) {
if (mopTemplate.templateName.equals(portalTemplate)) {
portalTemplates.put(portalName, mopTemplate);
break;
}
}
}
}
}
return portalTemplates;
}
private void resolveTemplate(final BackendServices svc,
SiteKey siteKey,
MopImport mopImport,
MopTemplate mopTemplate,
String owner)
throws Throwable {
if (mopTemplate.portalConfig != null) {
PortalConfig portalConfig = applyPattern(mopTemplate.portalConfig, owner);
portalConfig.setType(siteKey.getTypeName());
mopImport.siteTask = new SiteLayoutImportTask(portalConfig, siteKey, svc.dataStorage);
}
if (mopTemplate.pageSet != null) {
Page.PageSet pageSet = applyPattern(mopTemplate.pageSet, owner);
for (Page page : pageSet.getPages()) {
page.setOwnerType(siteKey.getTypeName());
page.setOwnerId(siteKey.getName());
}
// Obtains the site from the session when it's needed.
MOPSiteProvider siteProvider = new MOPSiteProvider() {
@Override
public Site getSite(SiteKey siteKey) {
return svc.mgr.getSession().getWorkspace()
.getSite(Utils.getObjectType(siteKey.getType()), siteKey.getName());
}
};
mopImport.pageTask = new PageImportTask(pageSet, siteKey, svc.dataStorage, svc.pageService, siteProvider);
}
if (mopTemplate.pageNavigation != null) {
PageNavigation pageNavigation = applyPattern(mopTemplate.pageNavigation, owner);
pageNavigation.setOwnerType(siteKey.getTypeName());
pageNavigation.setOwnerId(siteKey.getName());
mopImport.navigationTask = new NavigationImportTask(pageNavigation, siteKey, svc.navigationService,
svc.descriptionService, svc.dataStorage);
}
}
/**
* Applies "@owner@" pattern on following PortalConfig fields:
* - name
* - accessPermissions
* - editPermissions
*
* "@owner@" pattern is not expected outside these fields.
*
* Note:
* If pattern can appear in more fields may be it is better to store template as xml representation
* and unmarshall after pattern replace
*
* @param template PortalConfig created from a xml template
* @param owner final user who replace "@owner@" string in xml template
* @return new PortalConfig object with @owner@ replaced
*/
private PortalConfig applyPattern(PortalConfig template, String owner) {
PortalConfig portalConfig = PageUtils.copy(template);
if (portalConfig.getName() != null) {
portalConfig.setName( portalConfig.getName().replaceAll(OWNER, owner) );
}
if (portalConfig.getAccessPermissions() != null) {
for (int i=0; i<portalConfig.getAccessPermissions().length; i++) {
if (portalConfig.getAccessPermissions()[i] != null) {
portalConfig.getAccessPermissions()[i] = portalConfig.getAccessPermissions()[i].replaceAll(OWNER, owner);
}
}
}
if (portalConfig.getEditPermission() != null) {
portalConfig.setEditPermission( portalConfig.getEditPermission().replaceAll(OWNER, owner) );
}
return portalConfig;
}
/**
* Applies "@owner@" pattern on following PageSet fields:
* - name
* - title
* - accessPermissions
* - editPermissions
*
* "@owner@" pattern is not expected outside these fields.
*
* Note:
* If pattern can appear in more fields may be it is better to store template as xml representation
* and unmarshall after pattern replace
*
* @param template PageSet created from a xml template
* @param owner final user who replace "@owner@" string in xml template
* @return new PageSet object with @owner@ replaced
*/
private Page.PageSet applyPattern(Page.PageSet template, String owner) {
Page.PageSet pageSet = PageUtils.copy(template);
for (Page page : pageSet.getPages()) {
if (page.getName() != null) {
page.setName( page.getName().replaceAll(OWNER, owner) );
}
if (page.getTitle() != null) {
page.setTitle( page.getTitle().replaceAll(OWNER, owner) );
}
if (page.getAccessPermissions() != null) {
for (int i=0; i<page.getAccessPermissions().length; i++) {
if (page.getAccessPermissions()[i] != null) {
page.getAccessPermissions()[i] = page.getAccessPermissions()[i].replaceAll(OWNER, owner);
}
}
}
if (page.getEditPermission() != null) {
page.setEditPermission( page.getEditPermission().replaceAll(OWNER, owner) );
}
}
return pageSet;
}
/**
* Applies "@owner@" pattern on following PageNavigation fields:
* - pageReference of PageNode objects
*
* "@owner@" pattern is not expected outside these fields.
*
* Note:
* If pattern can appear in more fields may be it is better to store template as xml representation
* and unmarshall after pattern replace
*
* @param template PageNavigation created from a xml template
* @param owner final user who replace "@owner@" string in xml template
* @return new PageNavigation object with @owner@ replaced
*/
private PageNavigation applyPattern(PageNavigation template, String owner) {
PageNavigation pageNavigation = NavigationUtils.copy(template);
for (NavigationFragment fragment : pageNavigation.getFragments()) {
for (PageNode pageNode : fragment.getNodes()) {
applyPattern(pageNode, owner);
}
}
return pageNavigation;
}
private void applyPattern(PageNode pageNode, String owner) {
if (pageNode.getPageReference() != null) {
pageNode.setPageReference( pageNode.getPageReference().replace(OWNER, owner) );
}
if (pageNode.getChildren() != null) {
for (PageNode child : pageNode.getChildren()) {
applyPattern(child, owner);
}
}
}
private Map<SiteKey, MopImport> expandGroupTemplate(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates) {
Map<SiteKey, MopImport> expanded = new HashMap<SiteKey, MopImport>();
try {
// Pairs of (groupName, MopTemplate) filtered by targetGroup
Map<String, MopTemplate> groupNamesTemplates = filterGroupNames(attr, svc, templates);
for (Map.Entry<String, MopTemplate> groupNameTemplate : groupNamesTemplates.entrySet()) {
String groupName = groupNameTemplate.getKey();
MopTemplate mopTemplate = groupNameTemplate.getValue();
SiteKey newSiteKey = new SiteKey(SiteType.GROUP, groupName);
MopImport newMopImport = new MopImport();
resolveTemplate(svc, newSiteKey, newMopImport, mopTemplate, groupName);
expanded.put(newSiteKey, newMopImport);
}
} catch (Throwable t) {
throw new OperationException("Import group template", t.getMessage(), t);
}
return expanded;
}
private Map<String, MopTemplate> filterGroupNames(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates)
throws Exception {
Map<String, MopTemplate> groupTemplates = new HashMap<String, MopTemplate>();
List<String> groupNames = svc.dataStorage.getAllGroupNames();
// Filters groups defined in targetGroup attributes
if (attr.targetGroup.size() > 0) {
Iterator<String> iterGroupNames = groupNames.iterator();
while (iterGroupNames.hasNext()) {
String name = iterGroupNames.next();
if (!attr.targetGroup.contains(name)) {
iterGroupNames.remove();
}
}
}
// Filters groups with Navigation activated
Iterator<String> iterGroupNames = groupNames.iterator();
while (iterGroupNames.hasNext()) {
String name = iterGroupNames.next();
NavigationContext navigation = svc.navigationService.loadNavigation(SiteKey.group(name));
if (navigation == null || navigation.getState() == null) {
iterGroupNames.remove();
}
}
for (String groupName : groupNames) {
for (MopTemplate mopTemplate : templates) {
if (mopTemplate.templateType.equals(SiteType.GROUP)) {
groupTemplates.put(groupName, mopTemplate);
}
}
}
return groupTemplates;
}
private Map<SiteKey, MopImport> expandUserTemplate(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates) {
Map<SiteKey, MopImport> expanded = new HashMap<SiteKey, MopImport>();
try {
// Pairs of (userName, MopTemplate) filtered by groups defined in targetUser attribute
Map<String, MopTemplate> userNamesTemplates = filterUserNames(attr, svc, templates);
for (Map.Entry<String, MopTemplate> userNameTemplate : userNamesTemplates.entrySet()) {
String userName = userNameTemplate.getKey();
MopTemplate mopTemplate = userNameTemplate.getValue();
SiteKey newSiteKey = new SiteKey(SiteType.USER, userName);
MopImport newMopImport = new MopImport();
resolveTemplate(svc, newSiteKey, newMopImport, mopTemplate, userName);
expanded.put(newSiteKey, newMopImport);
}
} catch (Throwable t) {
throw new OperationException("Import user template", t.getMessage(), t);
}
return expanded;
}
private Map<String, MopTemplate> filterUserNames(OperationAttributes attr,
BackendServices svc,
List<MopTemplate> templates)
throws Throwable {
Map<String, MopTemplate> userTemplates = new HashMap<String, MopTemplate>();
List<String> userNames = new ArrayList<String>();
ListAccess<User> lUsers;
// targetExpr attribute has preference over targetUser attribute
if (attr.targetExpr != null && !"".equals(attr.targetExpr)) {
Query qUsers = new Query();
qUsers.setUserName(attr.targetExpr);
lUsers = svc.organizationService.getUserHandler().findUsersByQuery(qUsers);
for (User u : lUsers.load(0, lUsers.getSize())) {
userNames.add(u.getUserName());
}
} else if (attr.targetUser != null && attr.targetUser.size() > 0) {
// Validates that a user exists
for (String u : attr.targetUser) {
User user = svc.organizationService.getUserHandler().findUserByName(u);
if (user != null) {
userNames.add(user.getUserName());
}
}
} else {
lUsers = svc.organizationService.getUserHandler().findAllUsers();
for (User u : lUsers.load(0, lUsers.getSize())) {
userNames.add(u.getUserName());
}
}
if (attr.dashboardMode == null || !CREATE.equalsIgnoreCase(attr.dashboardMode)) {
// Filters users with dashboard created
Iterator<String> iterNames = userNames.iterator();
while (iterNames.hasNext()) {
String userName = iterNames.next();
PortalConfig portalConfig = svc.dataStorage.getPortalConfig("user", userName);
if (portalConfig == null) {
iterNames.remove();
}
}
}
for (String userName : userNames) {
for (MopTemplate mopTemplate : templates) {
if (mopTemplate.templateType.equals(SiteType.USER)) {
userTemplates.put(userName, mopTemplate);
}
}
}
return userTemplates;
}
private void validationRules(OperationAttributes attr, Map<SiteKey, MopImport> importMap) {
for (Map.Entry<SiteKey, MopImport> mopImportEntry : importMap.entrySet()) {
SiteKey siteKey = mopImportEntry.getKey();
MopImport mopImport = mopImportEntry.getValue();
// Rule #1: Send a warning if navigation.xml alone
if (mopImport.siteTask == null &&
mopImport.pageTask == null &&
mopImport.navigationTask != null) {
log.warn("Importing a template with only navigation.xml file. " +
"You should validate <page-reference> points to valid pages, " +
"if not this can create unstable references.");
}
// Rule #2: Send a warning if navigation.xml <page-reference> doesn't match with siteKey type
List<String> refPages = new ArrayList<String>();
if (mopImport.navigationTask != null) {
PageNavigation pageNavigation = mopImport.navigationTask.getData();
SiteType siteType = siteKey.getType();
for (NavigationFragment fragment : pageNavigation.getFragments()) {
validateNodeType(siteType, fragment.getNodes(), refPages);
}
}
// Rule #3: Send a warning if navigation.xml has <page-reference> without pointing to pages.xml
if (mopImport.pageTask != null &&
mopImport.navigationTask != null) {
Page.PageSet pageSet = mopImport.pageTask.getData();
if (pageSet != null) {
for (Page page : pageSet.getPages()) {
String name = page.getName();
if (!refPages.contains(name)) {
log.warn("pages.xml contains <page> not referenced on navigation.xml");
}
}
}
}
// Rule #4: Send a warning if dashboardMode == create and missing some {user,pages,navigation}.xml file
if (siteKey.getType() == SiteType.USER &&
CREATE.equalsIgnoreCase(attr.dashboardMode) &&
( mopImport.siteTask == null ||
mopImport.pageTask == null ||
mopImport.navigationTask == null )) {
log.warn("dashboardMode == " + attr.dashboardMode +
" and missing some {user,pages,navigation}.xml in .zip file.");
}
}
}
private void validateNodeType(SiteType siteType, List<PageNode> nodes, List<String> refPages) {
if (nodes != null) {
for (PageNode node : nodes) {
String pageReference = node.getPageReference();
if (pageReference != null) {
if (pageReference.startsWith("portal") && siteType != SiteType.PORTAL) {
log.warn("Detected navigation.xml with <page-reference> pointing to a portal pages for a site type "
+ siteType + ".");
} else if (pageReference.startsWith("group") && siteType != SiteType.GROUP) {
log.warn("Detected navigation.xml with <page-reference> pointing to a group pages for a site type "
+ siteType + ".");
} else if (pageReference.startsWith("user") && siteType != SiteType.USER) {
log.warn("Detected navigation.xml with <page-reference> pointing to a user pages for a site type "
+ siteType + ".");
}
// Save referenced page for other validations
String[] chunks = pageReference.split("::");
if (chunks.length == 3) {
refPages.add(chunks[2]);
}
}
if (node.getChildren() != null) {
validateNodeType(siteType, node.getChildren(), refPages);
}
}
}
}
private OperationException performImport(OperationAttributes attr, Map<SiteKey, MopImport> importMap)
throws OperationException {
// Performs import
Map<SiteKey, MopImport> importsRan = new HashMap<SiteKey, MopImport>();
OperationException importError = null;
try {
log.info("Performing import using importMode '" + attr.importMode + "'");
for (Map.Entry<SiteKey, MopImport> mopImportEntry : importMap.entrySet()) {
SiteKey siteKey = mopImportEntry.getKey();
MopImport mopImport = mopImportEntry.getValue();
MopImport ran = new MopImport();
if (importsRan.containsKey(siteKey)) {
throw new IllegalStateException("Multiple site imports for same operation.");
}
importsRan.put(siteKey, ran);
log.debug("Importing data for site " + siteKey);
// Site layout import
if (mopImport.siteTask != null) {
log.debug("Importing site layout data.");
ran.siteTask = mopImport.siteTask;
mopImport.siteTask.importData(attr.importMode);
}
// Pages import
if (mopImport.pageTask != null) {
log.debug("Importing page data.");
ran.pageTask = mopImport.pageTask;
mopImport.pageTask.importData(attr.importMode);
}
// Navigation import
if (mopImport.navigationTask != null) {
log.debug("Importing navigation data.");
ran.navigationTask = mopImport.navigationTask;
mopImport.navigationTask.importData(attr.importMode);
}
}
log.info("Import successful !");
} catch (Throwable t) {
boolean rollbackSuccess = true;
log.error("Exception importing data.", t);
log.info("Attempting to rollback data modified by import.");
for (Map.Entry<SiteKey, MopImport> mopImportEntry : importsRan.entrySet()) {
SiteKey siteKey = mopImportEntry.getKey();
MopImport mopImport = mopImportEntry.getValue();
log.debug("Rolling back imported data for site " + siteKey);
if (mopImport.navigationTask != null) {
log.debug("Rolling back navigation modified during import...");
try {
mopImport.navigationTask.rollback();
} catch (Throwable t1) // Continue rolling back even though there are exceptions.
{
rollbackSuccess = false;
log.error("Error rolling back navigation data for site " + siteKey, t1);
}
}
if (mopImport.pageTask != null) {
log.debug("Rolling back pages modified during import...");
try {
mopImport.pageTask.rollback();
} catch (Throwable t1) // Continue rolling back even though there are exceptions.
{
rollbackSuccess = false;
log.error("Error rolling back page data for site " + siteKey, t1);
}
}
if (mopImport.siteTask != null) {
log.debug("Rolling back site layout modified during import...");
try {
mopImport.siteTask.rollback();
} catch (Throwable t1) // Continue rolling back even though there are exceptions.
{
rollbackSuccess = false;
log.error("Error rolling back site layout for site " + siteKey, t1);
}
}
}
String message = (rollbackSuccess) ?
"Error during import. Tasks successfully rolled back. Portal should be back to consistent state."
: "Error during import. Errors in rollback as well. Portal may be in an inconsistent state.";
importError = new OperationException(attr.operationName, message, t);
} finally {
importMap.clear();
importsRan.clear();
}
return importError;
}
// See GTNPORTAL-3257
private static void endRequest(OperationAttributes attr, BackendServices svc, OperationException importError) {
OperationException error = importError;
try {
// End the request to flush out anything that might go wrong when finalizing the request.
svc.chromatticManager.endRequest(true);
} catch (Throwable t) {
// This allows us to properly respond with an error (500 in REST scenario) if ChromatticManager.endRequest fails
if (importError == null) {
log.error("Exception occurred ending the request of ChromatticManager after a successful import.", t);
error = new OperationException(attr.operationName,
"An exception occurred after a successful import. " +
"See server logs for more details");
} else {
log.error("Exception occurred ending the request of ChromatticManager after a failed import.", t);
}
} finally {
// Start it again, as the calling container ends all ComponentRequestLifecycle's,
// and we don't want the end to be called w/out it beginning again.
svc.chromatticManager.beginRequest();
}
if (error != null) {
throw error;
}
}
private static String[] parseEntry(ZipEntry entry, boolean portal)
throws IOException {
String name = entry.getName();
if (isSiteLayoutEntry(name) || name.endsWith(PageExportTask.FILE) || name.endsWith(NavigationExportTask.FILE)) {
String[] parts;
if (portal) {
parts = new String[4];
parts[0] = name.substring(0, name.indexOf("/"));
parts[1] = name.substring(parts[0].length() + 1, name.indexOf("/", parts[0].length() + 1));
parts[2] = name.substring(parts[0].length() + parts[1].length() + 2, name.indexOf("/", parts[0].length()
+ parts[1].length() + 3));
parts[3] = name.substring(name.lastIndexOf("/") + 1);
} else {
parts = new String[3];
parts[0] = name.substring(0, name.indexOf("/"));
parts[1] = name.substring(parts[0].length() + 1, name.lastIndexOf("/"));
parts[2] = name.substring(name.lastIndexOf("/") + 1);
}
return parts;
} else {
throw new IOException("Unknown entry " + name + " in zip file.");
}
}
private static boolean isSiteLayoutEntry(String zipEntryName) {
for (String file : SiteLayoutExportTask.FILES) {
if (zipEntryName.endsWith(file))
return true;
}
return false;
}
// Bug in SUN's JDK XMLStreamReader implementation closes the underlying stream when
// it finishes reading an XML document. This is no good when we are using a ZipInputStream.
// See http://bugs.sun.com/view_bug.do?bug_id=6539065 for more information.
private static class NonCloseableZipInputStream extends ZipInputStream {
private NonCloseableZipInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public void close() throws IOException {
}
private void reallyClose() throws IOException {
super.close();
}
}
private static class MopImport {
private SiteLayoutImportTask siteTask;
private PageImportTask pageTask;
private NavigationImportTask navigationTask;
}
private static class OperationAttributes {
private String operationName;
private String importType;
private String mode;
private ImportMode importMode;
private String targetExpr;
private List<String> targetUser;
private String dashboardMode;
private List<String> targetGroup;
private List<String> targetSite;
}
private static class BackendServices {
private Workspace workspace = null;
private DataStorage dataStorage = null;
private PageService pageService = null;
private NavigationService navigationService = null;
private DescriptionService descriptionService = null;
private POMSessionManager mgr = null;
private OrganizationService organizationService = null;
private ChromatticManager chromatticManager = null;
}
private static class MopTemplate {
private String templateName;
private SiteType templateType;
private PortalConfig portalConfig;
private Page.PageSet pageSet;
private PageNavigation pageNavigation;
}
private static class ZipTemplateException extends Exception {
public ZipTemplateException(String message) {
super(message);
}
}
}