001/* 002 * Copyright 2020 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; 020 021import javax.jcr.ImportUUIDBehavior; 022import javax.jcr.RepositoryException; 023import javax.jcr.Session; 024 025import org.apache.avalon.framework.activity.Disposable; 026import org.apache.avalon.framework.activity.Initializable; 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang.NotImplementedException; 032import org.apache.excalibur.source.SourceResolver; 033import org.slf4j.Logger; 034 035import org.ametys.plugins.repository.AmetysObject; 036import org.ametys.plugins.repository.AmetysObjectResolver; 037import org.ametys.plugins.repository.RemovableAmetysObject; 038import org.ametys.runtime.plugin.component.AbstractLogEnabled; 039 040/** 041 * The component holding the {@link Merger} implementations 042 */ 043public class Mergers extends AbstractLogEnabled implements Component, Serviceable, Initializable, Disposable 044{ 045 /** Avalon role. */ 046 public static final String ROLE = Mergers.class.getName(); 047 048 static final Merger DELETE_BEFORE = new DeleteBefore(); 049 static final Merger IGNORE = new Ignore(); 050 static final Merger REPLACE = new Replace(); 051 static final Merger FAIL = new Fail(); 052 053 private static final Merger[] __IMPLEMENTATIONS = new Merger[] {DELETE_BEFORE, IGNORE, REPLACE, FAIL}; 054 055 AmetysObjectResolver _ametysObjectResolver; 056 SourceResolver _sourceResolver; 057 058 @Override 059 public void service(ServiceManager manager) throws ServiceException 060 { 061 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 062 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 063 } 064 065 private static void _bindMergersComponent(Mergers mergersComponent) 066 { 067 for (Merger merger : __IMPLEMENTATIONS) 068 { 069 if (merger instanceof AbstractMerger) 070 { 071 ((AbstractMerger) merger).lateBindDependencies(mergersComponent); 072 } 073 } 074 } 075 076 @Override 077 public void initialize() throws Exception 078 { 079 _bindMergersComponent(this); 080 } 081 082 @Override 083 public void dispose() 084 { 085 _bindMergersComponent(null); 086 } 087 088 private abstract static class AbstractMerger implements Merger 089 { 090 /** The {@link Mergers} component to access to other components/services/dependencies */ 091 protected Mergers _mergers; 092 093 void lateBindDependencies(Mergers mergersComponent) 094 { 095 _mergers = mergersComponent; 096 } 097 } 098 099 // Override just to avoid warnings on usage of _mergers.getLogger() 100 @Override 101 protected Logger getLogger() 102 { 103 return super.getLogger(); 104 } 105 106 private static class DeleteBefore implements Merger 107 { 108 @Override 109 public boolean deleteBeforePartialImport() 110 { 111 return true; 112 } 113 114 @Override 115 public boolean needsMerge(String id) 116 { 117 return false; 118 } 119 120 @Override 121 public AfterMerge merge(String id) 122 { 123 throw new NotImplementedException("Should never be called"); 124 } 125 126 @Override 127 public int getImportUuidBehavior() 128 { 129 return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW; // should never have a collision, so choose here to throw 130 } 131 } 132 133 private static class Ignore extends AbstractMerger 134 { 135 @Override 136 public boolean needsMerge(String id) 137 { 138 return _mergers._ametysObjectResolver.hasAmetysObjectForId(id); 139 } 140 141 @Override 142 public AfterMerge merge(String id) 143 { 144 // Nothing to do 145 // Indeed, we want to ignore the import of the object 146 // The already existing object is kept 147 // We return STOP_PROCESS so as to not import the object from the ZIP archive 148 _mergers.getLogger().debug("Object for id '{}' will be ignored as it already exists ('Ignore' MergePolicy)...", id); 149 return AfterMerge.STOP_PROCESS; 150 } 151 152 @Override 153 public int getImportUuidBehavior() 154 { 155 // There is no behavior for that 156 // So we return the safest option here, i.e. throw an error as soon as possible 157 return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW; 158 } 159 160 @Override 161 public void jcrImportXml(Session session, String parentAbsPath, InputStream in) throws RepositoryException, IOException 162 { 163 IgnoreMergerHelper.jcrImportXml(session, parentAbsPath, in, getImportUuidBehavior(), _mergers); 164 } 165 } 166 167 private static class Replace extends AbstractMerger 168 { 169 @Override 170 public boolean needsMerge(String id) 171 { 172 return _mergers._ametysObjectResolver.hasAmetysObjectForId(id); 173 } 174 175 @Override 176 public AfterMerge merge(String id) 177 { 178 // We remove the existing object 179 // Indeed, we want to replace this version by the one from the ZIP archive 180 // We return CONTINUE_PROCESS so as to import the object 181 AmetysObject ametysObject = _mergers._ametysObjectResolver.resolveById(id); 182 if (ametysObject instanceof RemovableAmetysObject) 183 { 184 _mergers.getLogger().debug("Removing existing object '{}' as it will be re-created ('Replace' MergePolicy)...", ametysObject); 185 ((RemovableAmetysObject) ametysObject).remove(); 186 return AfterMerge.CONTINUE_PROCESS; 187 } 188 else 189 { 190 throw new IllegalArgumentException(String.format( 191 "The given object (%s) for id '%s' cannot be merged with a 'Replace' MergePolicy, as the existing object is not a RemovableAmetysObject", 192 ametysObject, 193 id)); 194 } 195 } 196 197 @Override 198 public int getImportUuidBehavior() 199 { 200 return ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING; 201 } 202 } 203 204 private static class Fail extends AbstractMerger 205 { 206 @Override 207 public boolean needsMerge(String id) 208 { 209 return _mergers._ametysObjectResolver.hasAmetysObjectForId(id); 210 } 211 212 @Override 213 public AfterMerge merge(String id) throws MergeException 214 { 215 AmetysObject ametysObject = _mergers._ametysObjectResolver.resolveById(id); 216 throw new MergeException(String.format( 217 "The import failed because the object with id '%s' already exists and the 'Fail' MergePolicy was selected.", 218 ametysObject, 219 id)); 220 } 221 222 @Override 223 public int getImportUuidBehavior() 224 { 225 return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW; 226 } 227 } 228}