/* * Copyright (C) 2003-2017 eXo Platform SAS. * * 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.management.content.operations.site.contents; import org.apache.commons.lang.StringUtils; import org.exoplatform.commons.utils.ActivityTypeUtils; import org.exoplatform.management.common.FileEntry; import org.exoplatform.management.common.exportop.ActivitiesExportTask; import org.exoplatform.management.common.exportop.JCRNodeExportTask; import org.exoplatform.management.common.importop.AbstractJCRImportOperationHandler; import org.exoplatform.management.common.importop.ActivityImportOperationInterface; import org.exoplatform.management.common.importop.FileImportOperationInterface; import org.exoplatform.management.content.operations.site.SiteUtil; import org.exoplatform.management.content.operations.site.seo.SiteSEOExportTask; import org.exoplatform.services.ecm.publication.PublicationService; import org.exoplatform.services.jcr.RepositoryService; import org.exoplatform.services.seo.PageMetadataModel; import org.exoplatform.services.seo.SEOService; import org.exoplatform.services.wcm.publication.WCMPublicationService; import org.exoplatform.social.core.activity.model.ExoSocialActivity; import org.exoplatform.social.core.manager.ActivityManager; import org.exoplatform.social.core.space.spi.SpaceService; import org.exoplatform.social.core.storage.api.ActivityStorage; import org.exoplatform.social.core.storage.api.IdentityStorage; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; import org.gatein.management.api.exceptions.OperationException; import org.gatein.management.api.operation.OperationContext; import org.gatein.management.api.operation.OperationNames; import org.gatein.management.api.operation.ResultHandler; import org.gatein.management.api.operation.model.NoResultModel; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.jcr.Node; import javax.jcr.Session; /** * The Class SiteContentsImportResource. * * @author <a href="mailto:soren.schmidt@exoplatform.com">Soren Schmidt</a> * @author <a href="mailto:thomas.delhomenie@exoplatform.com">Thomas * Delhoménie</a> * @author <a href="mailto:boubaker.khanfir@exoplatform.com">Boubaker * Khanfir</a> * @version $Revision$ usage: ssh -p 2000 john@localhost mgmt connect ls cd * content import -f /acmeTest.zip */ public class SiteContentsImportResource extends AbstractJCRImportOperationHandler implements ActivityImportOperationInterface, FileImportOperationInterface { /** The Constant log. */ private final static Logger log = LoggerFactory.getLogger(SiteContentsImportResource.class); /** The Constant CONTENT_LINK_PATTERN. */ private final static Pattern CONTENT_LINK_PATTERN = Pattern.compile("([a-zA-Z]*)/([a-zA-Z]*)/(.*)"); /** The seo service. */ private static SEOService seoService = null; /** The imported site name. */ private String importedSiteName = null; /** The file path. */ private String filePath = null; /** * Instantiates a new site contents import resource. */ public SiteContentsImportResource() {} /** * Instantiates a new site contents import resource. * * @param siteName the site name * @param filePath the file path */ public SiteContentsImportResource(String siteName, String filePath) { this.importedSiteName = siteName; this.filePath = filePath; } /** * {@inheritDoc} */ @Override public void execute(OperationContext operationContext, ResultHandler resultHandler) throws OperationException { spaceService = operationContext.getRuntimeContext().getRuntimeComponent(SpaceService.class); activityManager = operationContext.getRuntimeContext().getRuntimeComponent(ActivityManager.class); activityStorage = operationContext.getRuntimeContext().getRuntimeComponent(ActivityStorage.class); identityStorage = operationContext.getRuntimeContext().getRuntimeComponent(IdentityStorage.class); repositoryService = operationContext.getRuntimeContext().getRuntimeComponent(RepositoryService.class); seoService = operationContext.getRuntimeContext().getRuntimeComponent(SEOService.class); publicationService = operationContext.getRuntimeContext().getRuntimeComponent(PublicationService.class); wcmPublicationService = operationContext.getRuntimeContext().getRuntimeComponent(WCMPublicationService.class); if (importedSiteName == null) { importedSiteName = operationContext.getAddress().resolvePathTemplate("site-name"); } List<String> filters = operationContext.getAttributes().getValues("filter"); boolean isCleanPublication = filters.contains("cleanPublication:true"); boolean errors = false; InputStream attachmentInputStream = null; increaseCurrentTransactionTimeOut(operationContext); try { if (filePath != null) { attachmentInputStream = new FileInputStream(filePath); } else { attachmentInputStream = getAttachementInputStream(operationContext); } // extract data from zip Map<String, List<FileEntry>> contentsByOwner = extractDataFromZip(attachmentInputStream); for (String site : contentsByOwner.keySet()) { List<FileEntry> fileEntries = contentsByOwner.get(site); FileEntry activitiesFile = getAndRemoveFileByPath(fileEntries, ActivitiesExportTask.FILENAME); List<FileEntry> seoFiles = getAndRemoveFilesStartsWith(fileEntries, SiteSEOExportTask.FILENAME); SiteMetaData siteMetaData = getSiteMetadata(fileEntries); Map<String, String> metaDataOptions = siteMetaData.getOptions(); String workspace = metaDataOptions.get("site-workspace"); log.info("Reading metadata options for import: workspace: " + workspace); try { if (fileEntries != null) { for (FileEntry fileEntry : fileEntries) { errors |= !importNode(fileEntry, workspace, isCleanPublication); } } log.info("Content import has been finished"); } catch (Exception e) { log.error("Error while importing site content: " + site, e); } // Delete String removeNodes = metaDataOptions.get("removeNodes"); if (!StringUtils.isEmpty(removeNodes)) { String[] removeNodePaths = removeNodes.split(";"); removeNodes(workspace, removeNodePaths); } // Import activities if (activitiesFile != null && activitiesFile.getFile().exists() && activityManager != null) { log.info("Importing Site Content activities"); importActivities(activitiesFile.getFile(), null, true); } if (seoFiles != null && !seoFiles.isEmpty()) { for (FileEntry fileEntry : seoFiles) { String lang = fileEntry.getNodePath().replace(SiteSEOExportTask.FILENAME, ""); File seoFile = fileEntry.getFile(); @SuppressWarnings("unchecked") List<PageMetadataModel> models = deserializeObject(seoFile, List.class, "seo"); if (models != null && !models.isEmpty()) { for (PageMetadataModel pageMetadataModel : models) { seoService.storeMetadata(pageMetadataModel, site, false, lang); } } } } } } catch (Exception e) { throw new OperationException(OperationNames.IMPORT_RESOURCE, "Unable to import Site contents.", e); } finally { restoreDefaultTransactionTimeOut(repositoryService); if (attachmentInputStream != null) { try { attachmentInputStream.close(); } catch (IOException e) { // Nothing to do } } } if (errors) { throw new OperationException(OperationNames.IMPORT_RESOURCE, "Some errors occured while importing contents."); } else { resultHandler.completed(NoResultModel.INSTANCE); } } /** * Removes the nodes. * * @param workspace the workspace * @param removeNodePaths the remove node paths * @throws Exception the exception */ private void removeNodes(String workspace, String[] removeNodePaths) throws Exception { Session session = getSession(workspace); for (String nodeToRemovePath : removeNodePaths) { if (StringUtils.isEmpty(nodeToRemovePath) || !nodeToRemovePath.startsWith("/")) { log.warn("Ignore node to delete: '" + nodeToRemovePath + "'"); continue; } if (session.itemExists(nodeToRemovePath)) { log.info("Remove node '" + nodeToRemovePath + "'"); session.getItem(nodeToRemovePath).remove(); session.save(); session.refresh(false); } else { log.warn("Cannot remove node because it doesn't exist '" + nodeToRemovePath + "'"); } } } /** * Gets the site metadata. * * @param fileEntries the file entries * @return the site metadata * @throws Exception the exception */ public static SiteMetaData getSiteMetadata(List<FileEntry> fileEntries) throws Exception { FileEntry metadataFile = getAndRemoveFileByPath(fileEntries, SiteMetaDataExportTask.FILENAME); if (metadataFile == null) { throw new IllegalStateException("Cannot retrieve site metadata file for site"); } SiteMetaData siteMetaData = deserializeObject(metadataFile.getFile(), SiteMetaData.class, "metadata"); if (siteMetaData == null) { throw new IllegalStateException("Cannot retrieve site metadata for site"); } return siteMetaData; } /** * {@inheritDoc} */ public String getManagedFilesPrefix() { return "content/sites/"; } /** * {@inheritDoc} */ public boolean isUnKnownFileFormat(String filePath) { return !filePath.endsWith(".xml") && !filePath.endsWith(SiteMetaDataExportTask.FILENAME) && !filePath.endsWith(SiteSEOExportTask.FILENAME) && !filePath.endsWith(SiteContentsVersionHistoryExportTask.VERSION_HISTORY_FILE_SUFFIX) && !filePath.endsWith(ActivitiesExportTask.FILENAME); } /** * {@inheritDoc} */ public boolean addSpecialFile(List<FileEntry> fileEntries, String filePath, File file) { if (filePath.endsWith(SiteMetaDataExportTask.FILENAME)) { fileEntries.add(new FileEntry(SiteMetaDataExportTask.FILENAME, file)); return true; } else if (filePath.endsWith(SiteSEOExportTask.FILENAME)) { String lang = filePath.substring(filePath.lastIndexOf("/") + 1, filePath.indexOf(SiteSEOExportTask.FILENAME)); fileEntries.add(new FileEntry(SiteSEOExportTask.FILENAME + lang, file)); return true; } else if (filePath.endsWith(ActivitiesExportTask.FILENAME)) { fileEntries.add(new FileEntry(ActivitiesExportTask.FILENAME, file)); return true; } else if (filePath.endsWith(SiteContentsVersionHistoryExportTask.VERSION_HISTORY_FILE_SUFFIX)) { String path = filePath.replace(SiteContentsVersionHistoryExportTask.VERSION_HISTORY_FILE_SUFFIX, ""); String[] fileParts = path.split(JCRNodeExportTask.JCR_DATA_SEPARATOR); if (fileParts.length != 2) { log.warn("Cannot parse Version History file: " + filePath); return true; } FileEntry fileEntry = getAndRemoveFileByPath(fileEntries, fileParts[1]); if (fileEntry == null) { log.error("Cannot parse file '" + filePath + "', no XML file found for this Version History file for node path: " + fileParts[1]); return true; } fileEntry.setHistoryFile(file); fileEntries.add(fileEntry); return true; } return false; } /** * {@inheritDoc} */ public String extractIdFromPath(String path) { int beginIndex = SiteUtil.getSitesBasePath().length() + 1; return path.substring(beginIndex, path.indexOf("/", beginIndex)); } /** * {@inheritDoc} */ public void attachActivityToEntity(ExoSocialActivity activity, ExoSocialActivity comment) throws Exception { if (comment != null) { return; } String contentLink = activity.getTemplateParams().get("contenLink"); Matcher matcher = CONTENT_LINK_PATTERN.matcher(contentLink); if (matcher.matches()) { String workspace = matcher.group(2); String contentPath = "/" + matcher.group(3); Session session = null; try { session = getSession(workspace); ActivityTypeUtils.attachActivityId(((Node) session.getItem(contentPath)), activity.getId()); session.save(); } finally { if (session != null) { session.logout(); } } } else { log.warn("Cannot attach activity for content activity with contentLink = " + contentLink); } } /** * {@inheritDoc} */ public boolean isActivityNotValid(ExoSocialActivity activity, ExoSocialActivity comment) throws Exception { if (comment == null) { String contentLink = activity.getTemplateParams().get("contenLink"); String workspace = null; String contentPath = null; if (contentLink != null && !contentLink.isEmpty()) { Matcher matcher = CONTENT_LINK_PATTERN.matcher(contentLink); if (matcher.matches() && matcher.groupCount() == 3) { workspace = matcher.group(2); contentPath = "/" + matcher.group(3); } if (workspace == null || workspace.isEmpty()) { log.warn("ContentLink param was found in activity params, but it doesn't refer to a correct path: " + contentLink); return true; } Session session = null; try { session = getSession(workspace); if (!session.itemExists(contentPath)) { log.warn("Document '" + contentPath + "' not found. Cannot import activity '" + activity.getTitle() + "'."); return true; } else { // delete old activity Node node = (Node) session.getItem(contentPath); if (node.isNodeType("exo:activityInfo") && activityManager != null) { String activityId = ActivityTypeUtils.getActivityId(node); deleteActivity(activityId); } } } finally { if (session != null) { session.logout(); } } } } return false; } }