001/* 002 * Copyright 2019 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.contentio.archive; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.nio.file.Path; 021import java.util.Collections; 022import java.util.zip.ZipEntry; 023import java.util.zip.ZipOutputStream; 024 025import javax.jcr.Node; 026import javax.jcr.RepositoryException; 027import javax.jcr.Session; 028import javax.xml.transform.sax.TransformerHandler; 029import javax.xml.transform.stream.StreamResult; 030 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.xml.sax.ContentHandler; 035 036import org.ametys.plugins.repository.AmetysObjectFactoryExtensionPoint; 037import org.ametys.plugins.repository.RepositoryConstants; 038import org.ametys.plugins.repository.collection.AmetysObjectCollection; 039import org.ametys.plugins.repository.collection.AmetysObjectCollectionFactory; 040import org.ametys.runtime.plugin.component.AbstractLogEnabled; 041 042/** 043 * Default implementation of a {@link PluginArchiver}. It uses the JCR system view for all data but contents. 044 * For contents, the {@link ContentsArchiverHelper} is used. 045 */ 046public class DefaultPluginArchiver extends AbstractLogEnabled implements PluginArchiver, Serviceable 047{ 048 /** Id for default implementation */ 049 public static final String EXTENSION_ID = "__default"; 050 051 private static final String __CONTENT_ROOT_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX + ":contents"; 052 053 private ContentsArchiverHelper _contentsArchiverHelper; 054 private AmetysObjectCollectionFactory _ametysObjectCollectionFactory; 055 056 @Override 057 public void service(ServiceManager manager) throws ServiceException 058 { 059 _contentsArchiverHelper = (ContentsArchiverHelper) manager.lookup(ContentsArchiverHelper.ROLE); 060 AmetysObjectFactoryExtensionPoint ametysObjectFactoryEP = (AmetysObjectFactoryExtensionPoint) manager.lookup(AmetysObjectFactoryExtensionPoint.ROLE); 061 _ametysObjectCollectionFactory = (AmetysObjectCollectionFactory) ametysObjectFactoryEP.getExtension(AmetysObjectCollectionFactory.class.getName()); 062 } 063 064 @Override 065 public void export(String pluginName, Node pluginNode, ZipOutputStream zos, String prefix) throws IOException 066 { 067 // first export non-content data with the JCR system view 068 ZipEntry pluginEntry = new ZipEntry(prefix + "/plugin.xml"); 069 zos.putNextEntry(pluginEntry); 070 071 try 072 { 073 TransformerHandler handler = Archivers.newTransformerHandler(); 074 handler.setResult(new StreamResult(zos)); 075 076 pluginNode.getSession().exportSystemView(pluginNode.getPath(), getSystemViewHandler(handler), false, false); 077 078 // then the contents if any, one by one 079 if (pluginNode.hasNode(__CONTENT_ROOT_NODE_NAME)) 080 { 081 _contentsArchiverHelper.exportContents(prefix + "/contents/", pluginNode.getNode("ametys:contents"), zos); 082 } 083 } 084 catch (Exception e) 085 { 086 throw new RuntimeException("Unable to archive plugin " + pluginName, e); 087 } 088 } 089 090 /** 091 * Returns the actual handler receiving the JCR system view. May be used to filter out some parts of the JCR export. 092 * @param initialHandler the target {@link ContentHandler}. 093 * @return a ContentHandler. 094 */ 095 protected ContentHandler getSystemViewHandler(ContentHandler initialHandler) 096 { 097 return new SystemViewHandler(initialHandler, name -> "ametys:contents".equals(name), __ -> false); 098 } 099 100 @Override 101 public ImportReport partialImport(String pluginName, Node allPluginsNode, Path zipPath, String zipPluginEntryPath, Merger merger) throws IOException 102 { 103 _importPluginXml(allPluginsNode, zipPath, zipPluginEntryPath, merger); 104 ImportReport importContentReport = importContentsIfAny(pluginName, allPluginsNode, zipPath, zipPluginEntryPath, merger); 105 return ImportReport.union(importContentReport); 106 } 107 108 private void _importPluginXml(Node allPluginsNode, Path zipPath, String zipPluginEntryPath, Merger merger) throws IOException 109 { 110 String zipPluginXmlEntryPath = zipPluginEntryPath + "/plugin.xml"; 111 try (InputStream in = ZipEntryHelper.zipEntryFileInputStream(zipPath, zipPluginXmlEntryPath)) 112 { 113 Session session = allPluginsNode.getSession(); 114 String parentAbsPath = allPluginsNode.getPath(); 115 getLogger().info("XML from '{}!{}' will be imported to '{}' with implementation of merger '{}'", zipPath, zipPluginXmlEntryPath, parentAbsPath, merger); 116 merger.jcrImportXml(session, parentAbsPath, in); 117 session.save(); 118 } 119 catch (RepositoryException e) 120 { 121 throw new IOException(e); 122 } 123 } 124 125 /** 126 * Import some contents if there is a folder named 'contents' 127 * @param pluginName the plugin name. 128 * @param allPluginsNode the {@link Node} for all plugins. 129 * @param zipPath The input ZIP file 130 * @param zipPluginEntryPath The input ZIP entry 131 * @param merger The {@link Merger} 132 * @return The {@link ImportReport} 133 * @throws IOException if an error occurs while reading the archive. 134 */ 135 protected ImportReport importContentsIfAny(String pluginName, Node allPluginsNode, Path zipPath, String zipPluginEntryPath, Merger merger) throws IOException 136 { 137 String baseImportContentPath = zipPluginEntryPath + "/contents"; 138 if (ZipEntryHelper.zipEntryFolderExists(zipPath, baseImportContentPath)) 139 { 140 try 141 { 142 // it must exist as #_importPluginXml must be called before 143 Node pluginNode = allPluginsNode.getNode(pluginName); 144 145 String pluginNodePath = pluginNode.getPath(); 146 AmetysObjectCollection rootContents = pluginNode.hasNode(__CONTENT_ROOT_NODE_NAME) 147 ? _ametysObjectCollectionFactory.getObject(pluginNodePath, pluginNode.getNode(__CONTENT_ROOT_NODE_NAME), pluginNodePath) 148 : _ametysObjectCollectionFactory.createChild(pluginNodePath, pluginNode, __CONTENT_ROOT_NODE_NAME, "ametys:collection"); 149 return _contentsArchiverHelper.importContents(baseImportContentPath + "/", rootContents, zipPath, merger, Collections.EMPTY_SET); 150 } 151 catch (RepositoryException e) 152 { 153 throw new IOException(e); 154 } 155 } 156 else 157 { 158 getLogger().info("No content to be imported for Plugin '{}', the path '{}!{}' does not exist", pluginName, zipPath, baseImportContentPath); 159 return new ImportReport(); 160 } 161 } 162}