001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (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 GmbH, 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.db; 029 030import org.opencms.main.CmsLog; 031import org.opencms.main.OpenCms; 032import org.opencms.util.CmsStringUtil; 033 034import java.io.File; 035import java.io.UnsupportedEncodingException; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039import java.util.Set; 040 041import org.apache.commons.logging.Log; 042 043import com.google.common.collect.Maps; 044import com.google.common.collect.Sets; 045 046/** 047 * An alternative export point driver which replaces the RFS targets of some export points with locations in a temporary export folder. 048 * 049 * This is designed specifically for export points which map to WEB-INF/lib and WEB-INF/classes. The problem with these is that replacing 050 * jar files or classes does not work at runtime. If these files are written to a temp folder (which is not picked up by the servlet coontainer's 051 * class loader) instead, an external script can be used to update the actual export point locations based on the temp folder while the servlet 052 * container is stopped. 053 * 054 * Since the script needs to know which files to delete in the 'real' (not temporary) export point locations, deletions of files which are mapped 055 * to the temp export point folder are not handled by deleting the files in that folder, but rather by replacing them with "dummy" files containing 056 * a specific marker string (which is contained in the DELETE_MARKER variable), so the script that updates the 'real' folder needs to delete files 057 * if it comes across a file starting with that marker string. 058 */ 059public class CmsTempFolderExportPointDriver extends CmsExportPointDriver { 060 061 /** The log object for this class. */ 062 @SuppressWarnings("unused") 063 private static final Log LOG = CmsLog.getLog(CmsTempFolderExportPointDriver.class); 064 065 /** The content to be used for dummy files in the temporary export point folders which represent deleted files. */ 066 public static final String DELETE_MARKER = "--OCMS-TEMP-EXPORTPOINT-DELETED-FILE-MARKER--"; 067 068 /** The byte array used to mark files as deleted. */ 069 protected static byte[] DELETE_MARKER_BYTES; 070 071 static { 072 try { 073 DELETE_MARKER_BYTES = DELETE_MARKER.getBytes("UTF-8"); 074 } catch (UnsupportedEncodingException e) { 075 e.printStackTrace(); // shouldn't happen 076 } 077 } 078 079 /** The temp folder for export points. */ 080 public static final String TEMP_EXPORTPOINT_FOLDER = "WEB-INF/exportpoint-temp"; 081 082 /** Map of export points by VFS path. */ 083 private Map<String, CmsExportPoint> m_exportPointMap = Maps.newHashMap(); 084 085 /** The URIs (VFS paths) of export points which are mapped to the temporary export point folder. */ 086 private Set<String> m_urisWithTempFolderDestinations = Sets.newHashSet(); 087 088 /** The list of RFS destinations which should be replaced with the corresponding destinations in the tmporary export point folder. */ 089 private List<String> m_tempFolderDestinations; 090 091 /** 092 * Constructor for a CmsExportPointDriver.<p> 093 * 094 * @param exportpoints the list of export points 095 * @param tempFolderDestinations the export point destinations (relative to the web application folder) which should be replaced with 096 */ 097 public CmsTempFolderExportPointDriver(Set<CmsExportPoint> exportpoints, List<String> tempFolderDestinations) { 098 099 super(); // Uses empty superclass constructor, i.e. we have to initialize members from parent class by hand 100 m_tempFolderDestinations = tempFolderDestinations; 101 m_exportpoints = exportpoints; 102 m_exportpointLookupMap = new HashMap<String, String>(); 103 for (CmsExportPoint point : m_exportpoints) { 104 String dest = point.getConfiguredDestination(); 105 m_exportPointMap.put(point.getUri(), point); 106 if (shouldUseTempFolderForDestination(dest)) { 107 String realDest = OpenCms.getSystemInfo().getAbsoluteRfsPathRelativeToWebApplication( 108 CmsStringUtil.joinPaths(TEMP_EXPORTPOINT_FOLDER, point.getConfiguredDestination())); 109 m_exportpointLookupMap.put(point.getUri(), realDest); 110 m_urisWithTempFolderDestinations.add(point.getUri()); 111 } else { 112 if (point.getDestinationPath() != null) { 113 // otherwise this point is not valid, but must be kept for serializing the configuration 114 m_exportpointLookupMap.put(point.getUri(), point.getDestinationPath()); 115 } 116 } 117 } 118 } 119 120 /** 121 * Removes a leading slash from a path string.<p> 122 * 123 * @param path the input string 124 * 125 * @return the path without leading slashes 126 */ 127 private static String removeLeadingSlash(String path) { 128 129 return path.replaceFirst("^/+", ""); 130 } 131 132 /** 133 * @see org.opencms.db.I_CmsExportPointDriver#deleteResource(java.lang.String, java.lang.String) 134 */ 135 @Override 136 public void deleteResource(String resourceName, String exportpoint) { 137 138 File file = getExportPointFile(resourceName, exportpoint); 139 if (m_urisWithTempFolderDestinations.contains(exportpoint)) { 140 if (resourceName.endsWith("/")) { 141 // leave folders in the temp exportpoint dir alone 142 return; 143 } 144 writeFile(resourceName, exportpoint, DELETE_MARKER_BYTES); 145 } else { 146 if (file.exists() && file.canWrite()) { 147 // delete the file (or folder) 148 file.delete(); 149 // also delete empty parent directories 150 File parent = file.getParentFile(); 151 if (parent.canWrite()) { 152 parent.delete(); 153 } 154 } 155 } 156 } 157 158 /** 159 * @see org.opencms.db.CmsExportPointDriver#writeFile(java.lang.String, java.lang.String, byte[]) 160 */ 161 @Override 162 public void writeFile(String resourceName, String exportpoint, byte[] content) { 163 164 StringBuffer exportpath = new StringBuffer(128); 165 exportpath.append(m_exportpointLookupMap.get(exportpoint)); 166 exportpath.append(resourceName.substring(exportpoint.length())); 167 File file = new File(exportpath.toString()); 168 writeResource(file, content); 169 CmsExportPoint point = m_exportPointMap.get(exportpoint); 170 // we can use object identity comparison because DELETE_MARKER_BYTES is not used from the outside 171 if ((content != DELETE_MARKER_BYTES) && shouldUseTempFolderForDestination(point.getConfiguredDestination())) { 172 // we already wrote the file to the temp folder location, 173 // now write it to the real location, but only if it is new 174 exportpath = new StringBuffer(128); 175 exportpath.append(point.getDestinationPath()); 176 exportpath.append(resourceName.substring(exportpoint.length())); 177 file = new File(exportpath.toString()); 178 if (!file.exists()) { 179 writeResource(file, content); 180 } 181 } 182 } 183 184 /** 185 * Returns true if the given RFS destination should be replaced with the corresponding destination in the temporary export point folder.<p> 186 * 187 * @param destination an export point RFS destination 188 * @return true if the corresponding export point destination in the temporary export point folder should be used 189 */ 190 private boolean shouldUseTempFolderForDestination(String destination) { 191 192 destination = removeLeadingSlash(destination); 193 for (String tempFolderDestination : m_tempFolderDestinations) { 194 tempFolderDestination = removeLeadingSlash(tempFolderDestination); 195 if (CmsStringUtil.isPrefixPath(tempFolderDestination, destination)) { 196 return true; 197 } 198 } 199 return false; 200 } 201 202}