001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.module; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsProject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.CmsVfsResourceNotFoundException; 035import org.opencms.importexport.CmsImportVersion10; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.util.CmsPath; 040import org.opencms.util.CmsStringUtil; 041import org.opencms.util.CmsUUID; 042 043import java.util.ArrayList; 044import java.util.Arrays; 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.List; 048import java.util.Map; 049 050import org.apache.commons.logging.Log; 051 052/** 053 * Module data read from a module zip file.<p> 054 */ 055public class CmsModuleImportData { 056 057 /** Logger instance for this class. */ 058 private static final Log LOG = CmsLog.getLog(CmsModuleImportData.class); 059 060 /** The CMS context. */ 061 private CmsObject m_cms; 062 063 /** The map of conflicting ids (keys are structure ids from the manifest, values are structure ids from VFS). */ 064 private Map<CmsUUID, CmsUUID> m_conflictingIds = new HashMap<>(); 065 066 /** The module metadata. */ 067 private CmsModule m_module; 068 069 /** Online CMS object. */ 070 private CmsObject m_onlineCms; 071 072 /** The list of resource data for each entry in the manifest. */ 073 private List<CmsResourceImportData> m_resources = new ArrayList<>(); 074 075 /** 076 * Adds the information for a single resource.<p> 077 * 078 * @param resourceData the information for a single resource 079 */ 080 public void addResource(CmsResourceImportData resourceData) { 081 082 m_resources.add(resourceData); 083 } 084 085 /** 086 * Checks if the installed module is updatable with the version from the import zip file.<p> 087 * 088 * @param cms the current CMS context 089 * 090 * @return true if the module is updatable 091 */ 092 public boolean checkUpdatable(CmsObject cms) { 093 094 CmsModule newModule = getModule(); 095 LOG.info("Checking if module " + newModule.getName() + " is updateable"); 096 String exportVersion = newModule.getExportVersion(); 097 CmsModule installedModule = OpenCms.getModuleManager().getModule(getModule().getName()); 098 if (!CmsModuleUpdater.checkCompatibleModuleResources(installedModule, newModule)) { 099 LOG.info("Module is not updateable because of incompatible module resources. "); 100 return false; 101 } 102 103 if ((exportVersion == null) || !exportVersion.equals("" + CmsImportVersion10.IMPORT_VERSION10)) { 104 LOG.info("Module is not updateable because the export version is not 10."); 105 return false; 106 } 107 108 try { 109 Map<CmsUUID, CmsResourceImportData> importResourcesById = new HashMap<>(); 110 Map<CmsPath, CmsResourceImportData> importResourcesByPath = new HashMap<>(); 111 Map<String, CmsUUID> structureIdsByParentIdAndName = new HashMap<>(); 112 Map<CmsUUID, Map<String, CmsUUID>> parentFolderMaps = new HashMap<>(); 113 for (CmsResourceImportData resData : getResourceData()) { 114 if (resData.hasStructureId()) { 115 importResourcesById.put(resData.getResource().getStructureId(), resData); 116 } 117 importResourcesByPath.put(new CmsPath(resData.getPath()), resData); 118 } 119 for (CmsResourceImportData resData : getResourceData()) { 120 String parentFolderStr = CmsResource.getParentFolder(resData.getPath()); 121 if (parentFolderStr != null) { 122 CmsResourceImportData parent = importResourcesByPath.get(new CmsPath(parentFolderStr)); 123 if ((parent != null) && parent.hasStructureId()) { 124 parentFolderMaps.put(parent.getResource().getStructureId(), new HashMap<>()); 125 String key = parent.getResource().getStructureId() + "/" + resData.getResource().getName(); 126 if (resData.hasStructureId()) { 127 structureIdsByParentIdAndName.put(key, resData.getResource().getStructureId()); 128 } 129 } 130 } 131 } 132 133 cms = getCms(); 134 CmsObject onlineCms = OpenCms.initCmsObject(cms); 135 m_onlineCms = onlineCms; 136 CmsProject online = cms.readProject(CmsProject.ONLINE_PROJECT_NAME); 137 onlineCms.getRequestContext().setCurrentProject(online); 138 139 // Reject imports which have an entry with (parentId=A,name=B,structureId=C1) if there is a VFS entry with (parentId=A,name=B,structureId=C2), unless 140 // C1 does not occur in the VFS and C2 does not occur in the import. 141 for (CmsUUID parentId : parentFolderMaps.keySet()) { 142 for (CmsObject cmsToRead : Arrays.asList(cms, onlineCms)) { 143 try { 144 CmsResource parent = cmsToRead.readResource(parentId, CmsResourceFilter.IGNORE_EXPIRATION); 145 List<CmsResource> children = cmsToRead.readResources( 146 parent, 147 CmsResourceFilter.IGNORE_EXPIRATION, 148 false); 149 for (CmsResource child : children) { 150 String key = parent.getStructureId() + "/" + child.getName(); 151 CmsUUID importStructureId = structureIdsByParentIdAndName.get(key); 152 if ((importStructureId != null) && !importStructureId.equals(child.getStructureId())) { 153 CmsResourceImportData importRes = importResourcesById.get(importStructureId); 154 if (child.isFile() 155 && importRes.getResource().isFile() 156 && !containsStructureId(child.getStructureId()) 157 && !vfsResourceWithStructureId(importStructureId)) { 158 m_conflictingIds.put(importStructureId, child.getStructureId()); 159 importRes.setSkipResourceIdCheck(true); 160 LOG.info( 161 "Permitting conflicting id in module update for resource " 162 + importRes.getPath() 163 + " because the id from the module is not present in the VFS and vice versa."); 164 } else { 165 LOG.info("Different structure id for same directory entry."); 166 return false; 167 } 168 } 169 170 } 171 172 } catch (CmsException e) { 173 LOG.info(e.getLocalizedMessage(), e); 174 175 } 176 } 177 } 178 179 for (CmsResourceImportData resData : getResourceData()) { 180 String importPath = CmsModuleUpdater.normalizePath(resData.getResource().getRootPath()); 181 if (resData.hasStructureId()) { 182 CmsUUID importId = resData.getResource().getStructureId(); 183 for (CmsObject cmsToRead : Arrays.asList(cms, onlineCms)) { 184 try { 185 CmsResource resourceFromVfs = cmsToRead.readResource(importPath, CmsResourceFilter.ALL); 186 if (!resourceFromVfs.getStructureId().equals(importId)) { 187 188 if (resData.getResource().isFile() 189 && resourceFromVfs.isFile() 190 && !containsStructureId(resourceFromVfs.getStructureId()) 191 && !vfsResourceWithStructureId(importId)) { 192 193 LOG.info( 194 "Permitting conflicting id in module update for resource " 195 + resData.getPath() 196 + " because the id from the module is not present in the VFS and vice versa."); 197 m_conflictingIds.put(importId, resourceFromVfs.getStructureId()); 198 199 // If we have different structure ids, but they don't conflict with anything else in the manifest or VFS, 200 // we don't compare resource ids. First, because having different resource ids is normal in this scenario, second 201 // because the resource in the VFS is deleted anyway during the module update. 202 resData.setSkipResourceIdCheck(true); 203 204 } else { 205 206 LOG.info( 207 "Module is not updateable because the same path is mapped to different structure ids in the import and in the VFS: " 208 + importPath); 209 return false; 210 } 211 } 212 if (!resData.isSkipResourceIdCheck() 213 && resData.getResource().isFile() 214 && !(resData.getResource().getResourceId().equals(resourceFromVfs.getResourceId()))) { 215 LOG.info( 216 "Module is not updateable because of a resource id conflict for " 217 + resData.getResource().getRootPath()); 218 return false; 219 } 220 } catch (CmsVfsResourceNotFoundException e) { 221 // ignore 222 } 223 } 224 225 try { 226 CmsResource vfsResource = cms.readResource(importId, CmsResourceFilter.ALL); 227 if (vfsResource.isFolder() != resData.getResource().isFolder()) { 228 LOG.info( 229 "Module is not updateable because the same id belongs to a file in the import and a folder in the VFS, or vice versa"); 230 return false; 231 } 232 } catch (CmsVfsResourceNotFoundException e) { 233 // ignore 234 } 235 } else { 236 CmsModule module = getModule(); 237 boolean included = false; 238 boolean excluded = false; 239 for (String res : module.getResources()) { 240 if (CmsStringUtil.isPrefixPath(res, resData.getPath())) { 241 included = true; 242 break; 243 } 244 } 245 for (String res : module.getExcludeResources()) { 246 if (CmsStringUtil.isPrefixPath(res, resData.getPath())) { 247 excluded = true; 248 break; 249 } 250 } 251 if (included && !excluded) { 252 LOG.info( 253 "Module is not updateable because one of the resource entries included in the module resources has no structure id in the manifest."); 254 return false; 255 } 256 } 257 258 } 259 return true; 260 } catch (CmsException e) { 261 LOG.info("Module is not updateable because of error: " + e.getLocalizedMessage(), e); 262 return false; 263 } 264 } 265 266 /** 267 * Gets the CMS object.<p> 268 * 269 * @return the CMS object 270 */ 271 public CmsObject getCms() { 272 273 return m_cms; 274 } 275 276 /** 277 * Gets the map of conflicting ids.<p> 278 * 279 * The keys are structure ids from the manifest, the values are structure ids from the VFS. 280 * 281 * @return the conflicting id map 282 */ 283 public Map<CmsUUID, CmsUUID> getConflictingIds() { 284 285 return m_conflictingIds; 286 } 287 288 /** 289 * Gets the module metadata from the import zip.<p> 290 * 291 * @return the module metadata 292 */ 293 public CmsModule getModule() { 294 295 return m_module; 296 } 297 298 /** 299 * Gets the list of resource data objects for the manifest entries.<p> 300 * 301 * @return the resource data objects 302 */ 303 public List<CmsResourceImportData> getResourceData() { 304 305 return Collections.unmodifiableList(m_resources); 306 } 307 308 /** 309 * Sets the CMS object.<p> 310 * 311 * @param cms the CMS object to set 312 */ 313 public void setCms(CmsObject cms) { 314 315 m_cms = cms; 316 } 317 318 /** 319 * Sets the module metadata.<p> 320 * 321 * @param module the module metadata 322 */ 323 public void setModule(CmsModule module) { 324 325 m_module = module; 326 } 327 328 /** 329 * Check if the module data contains a given structure id.<p> 330 * 331 * @param structureId a structure id 332 * @return true if the module contains the given structure id 333 * 334 */ 335 private boolean containsStructureId(CmsUUID structureId) { 336 337 for (CmsResourceImportData res : m_resources) { 338 if (res.getResource().getStructureId().equals(structureId)) { 339 return true; 340 } 341 } 342 return false; 343 } 344 345 /** 346 * Checks if a resource with a given structure id exists in the VFS, either online or offline.<p> 347 * 348 * @param importId the structure id to check 349 * 350 * @return true if the VFS contains a resource with the given id 351 */ 352 private boolean vfsResourceWithStructureId(CmsUUID importId) { 353 354 return m_cms.existsResource(importId, CmsResourceFilter.ALL) 355 || m_onlineCms.existsResource(importId, CmsResourceFilter.ALL); 356 } 357 358}