001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.gwt; 029 030import org.opencms.db.CmsDriverManager; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.main.CmsEvent; 034import org.opencms.main.CmsLog; 035import org.opencms.main.I_CmsEventListener; 036import org.opencms.main.OpenCms; 037import org.opencms.util.CmsCollectionsGenericWrapper; 038import org.opencms.util.CmsStringUtil; 039 040import java.io.ByteArrayInputStream; 041import java.io.File; 042import java.io.FileInputStream; 043import java.io.IOException; 044import java.io.InputStream; 045import java.net.MalformedURLException; 046import java.net.URL; 047import java.net.URLConnection; 048import java.text.ParseException; 049import java.util.List; 050import java.util.regex.Matcher; 051import java.util.regex.Pattern; 052 053import org.apache.commons.logging.Log; 054 055import com.google.gwt.user.server.rpc.SerializationPolicy; 056import com.google.gwt.user.server.rpc.SerializationPolicyLoader; 057 058/** 059 * This class contains the data that should be cached for a specific service class.<p> 060 * 061 * We cache instances of this class rather than caching instances of {@link CmsGwtService} directly because 062 * its superclass, {@link com.google.gwt.user.server.rpc.RemoteServiceServlet}, does some caching which we can't use because it doesn't 063 * take the distinction between online and offline requests into account. 064 * 065 * @since 8.0.0 066 * 067 */ 068public class CmsGwtServiceContext implements I_CmsEventListener { 069 070 /** The static log object for this class. */ 071 private static final Log LOG = CmsLog.getLog(CmsGwtServiceContext.class); 072 073 /** Vaadin widgetset serialization policy pattern. */ 074 private static final Pattern vaadinPattern = Pattern.compile("/(VAADIN/.*)$"); 075 076 /** Static GWT serialization policy pattern. */ 077 private static final Pattern staticPattern = Pattern.compile("/handleStatic/(?:.*?)/(.*)$"); 078 079 /** The name, which is used for debugging. */ 080 private String m_name; 081 082 /** The serialization policy path. */ 083 private String m_serializationPolicyPath; 084 085 /** The offline serialization policy. */ 086 private SerializationPolicy m_serPolicyOffline; 087 088 /** The online serialization policy. */ 089 private SerializationPolicy m_serPolicyOnline; 090 091 /** 092 * Creates a new service context object.<p> 093 * 094 * @param name an identifier which is used for debugging 095 */ 096 public CmsGwtServiceContext(String name) { 097 098 m_name = name; 099 100 // listen on VFS changes for serialization policies 101 OpenCms.addCmsEventListener( 102 this, 103 new int[] { 104 I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED, 105 I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED, 106 I_CmsEventListener.EVENT_RESOURCE_MODIFIED, 107 I_CmsEventListener.EVENT_RESOURCES_MODIFIED, 108 I_CmsEventListener.EVENT_RESOURCE_DELETED, 109 I_CmsEventListener.EVENT_PUBLISH_PROJECT, 110 I_CmsEventListener.EVENT_CLEAR_CACHES, 111 I_CmsEventListener.EVENT_CLEAR_ONLINE_CACHES, 112 I_CmsEventListener.EVENT_CLEAR_OFFLINE_CACHES}); 113 114 } 115 116 /** 117 * Checks if the serialization policy path refers to a resource loaded from a Jar (GWT resources served via /handleStatic or Vaadin widget set), and if so, returns the corresponding resource name for use in {@link ClassLoader#getResource(String)} 118 * 119 * @param path the path to check 120 * @return the classloader path for the resource (or null, if the path is not for a resource loaded from a Jar) 121 */ 122 private static String getStaticSerializationPolicyResourcePath(String path) { 123 124 // Note: the regexes used might seem overly broad; you'd think you'd only need to match cases where the VAADIN or handleStatic part 125 // comes directly after the context path. But there have been some weird non-reproducible cases where the serialization policy path 126 // also included the servlet name. For simplicity, we match handleStatic or VAADIN anywhere in the path, since the cases where this is actually 127 // a legitimate serialization policy in the VFS or RFS rather than a path in a JAR seem incredibly unlikely. 128 129 Matcher matcher = vaadinPattern.matcher(path); 130 String resourcePath = null; 131 if (matcher.find()) { 132 resourcePath = matcher.group(1); 133 return resourcePath; 134 } 135 matcher = staticPattern.matcher(path); 136 if (matcher.find()) { 137 resourcePath = "OPENCMS/" + matcher.group(1); 138 } 139 return resourcePath; 140 } 141 142 /** 143 * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent) 144 */ 145 public void cmsEvent(CmsEvent event) { 146 147 CmsResource resource = null; 148 List<CmsResource> resources = null; 149 150 switch (event.getType()) { 151 case I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED: 152 case I_CmsEventListener.EVENT_RESOURCE_MODIFIED: 153 Object change = event.getData().get(I_CmsEventListener.KEY_CHANGE); 154 if ((change != null) && change.equals(Integer.valueOf(CmsDriverManager.NOTHING_CHANGED))) { 155 // skip lock & unlock 156 return; 157 } 158 // a resource has been modified in a way that it *IS NOT* necessary also to clear 159 // lists of cached sub-resources where the specified resource might be contained inside. 160 resource = (CmsResource)event.getData().get(I_CmsEventListener.KEY_RESOURCE); 161 uncacheResource(resource); 162 break; 163 164 case I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED: 165 // a list of resources and all of their properties have been modified 166 resources = CmsCollectionsGenericWrapper.list(event.getData().get(I_CmsEventListener.KEY_RESOURCES)); 167 uncacheResources(resources); 168 break; 169 170 case I_CmsEventListener.EVENT_RESOURCE_MOVED: 171 case I_CmsEventListener.EVENT_RESOURCE_DELETED: 172 case I_CmsEventListener.EVENT_RESOURCES_MODIFIED: 173 // a list of resources has been modified 174 resources = CmsCollectionsGenericWrapper.list(event.getData().get(I_CmsEventListener.KEY_RESOURCES)); 175 uncacheResources(resources); 176 break; 177 178 case I_CmsEventListener.EVENT_CLEAR_ONLINE_CACHES: 179 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 180 m_serPolicyOnline = null; 181 break; 182 183 case I_CmsEventListener.EVENT_CLEAR_CACHES: 184 m_serPolicyOnline = null; 185 m_serPolicyOffline = null; 186 break; 187 188 case I_CmsEventListener.EVENT_CLEAR_OFFLINE_CACHES: 189 m_serPolicyOffline = null; 190 break; 191 192 default: 193 // noop 194 break; 195 } 196 } 197 198 /** 199 * @see java.lang.Object#toString() 200 */ 201 @Override 202 public String toString() { 203 204 return super.toString() + "(" + m_name + ")"; 205 } 206 207 /** 208 * Returns the serialization policy for the service.<p> 209 * 210 * @param cms the current CMS context 211 * @param moduleBaseURL the module's base URL 212 * @param strongName the strong name of the service 213 * 214 * @return the serialization policy for the given service 215 */ 216 protected SerializationPolicy getSerializationPolicy(CmsObject cms, String moduleBaseURL, String strongName) { 217 218 if (m_serializationPolicyPath == null) { 219 m_serializationPolicyPath = getSerializationPolicyPath(moduleBaseURL, strongName); 220 } 221 return getSerializationPolicy(cms); 222 } 223 224 /** 225 * Finds the path of the serialization policy file.<p> 226 * 227 * @param moduleBaseURL the GWT module's base url 228 * @param strongName the strong name of the service 229 * 230 * @return the serialization policy path 231 */ 232 protected String getSerializationPolicyPath(String moduleBaseURL, String strongName) { 233 234 // locate the serialization policy file in OpenCms 235 String modulePath = null; 236 try { 237 modulePath = new URL(moduleBaseURL).getPath(); 238 } catch (MalformedURLException ex) { 239 // moduleBaseUrl is bad 240 LOG.error(ex.getLocalizedMessage(), ex); 241 return null; 242 } catch (NullPointerException ex) { 243 // moduleBaseUrl is null 244 LOG.error(ex.getLocalizedMessage(), ex); 245 return null; 246 } 247 return SerializationPolicyLoader.getSerializationPolicyFileName(modulePath + strongName); 248 } 249 250 /** 251 * Returns the serialization policy, using lazy initialization.<p> 252 * 253 * @param cms the current cms context 254 * 255 * @return the serialization policy 256 */ 257 private SerializationPolicy getSerializationPolicy(CmsObject cms) { 258 259 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 260 if (online && (m_serPolicyOnline != null)) { 261 return m_serPolicyOnline; 262 } else if (!online && (m_serPolicyOffline != null)) { 263 return m_serPolicyOffline; 264 } 265 266 SerializationPolicy serializationPolicy = null; 267 268 // Open the RPC resource file and read its contents 269 InputStream is = null; 270 try { 271 // check if this is a static resource request 272 String staticResourcePath = getStaticSerializationPolicyResourcePath(m_serializationPolicyPath); 273 if (staticResourcePath != null) { 274 LOG.debug( 275 "Trying static serialization policy path: " 276 + m_serializationPolicyPath 277 + " => " 278 + staticResourcePath); 279 URL resourceURL = OpenCms.getSystemInfo().getClass().getClassLoader().getResource(staticResourcePath); 280 URLConnection connection; 281 connection = resourceURL.openConnection(); 282 is = connection.getInputStream(); 283 } else { 284 // try reading from the RFS 285 String rfsPath = m_serializationPolicyPath; 286 if (rfsPath.startsWith(OpenCms.getSystemInfo().getContextPath())) { 287 rfsPath = rfsPath.substring(OpenCms.getSystemInfo().getContextPath().length()); 288 } 289 rfsPath = CmsStringUtil.joinPaths(OpenCms.getSystemInfo().getWebApplicationRfsPath(), rfsPath); 290 File policyFile = new File(rfsPath); 291 if (policyFile.exists() && policyFile.canRead()) { 292 is = new FileInputStream(policyFile); 293 } else { 294 // the file does not exist in the RFS, try the VFS 295 String policyPath = OpenCms.getLinkManager().getRootPath(cms, m_serializationPolicyPath); 296 is = new ByteArrayInputStream(cms.readFile(policyPath).getContents()); 297 } 298 } 299 } catch (Exception ex) { 300 // most likely file not found 301 String message = "ERROR: The serialization policy file '" 302 + m_serializationPolicyPath 303 + "' was not found; did you forget to include it in this deployment?"; 304 LOG.warn(message); 305 LOG.warn(ex.getLocalizedMessage(), ex); 306 307 } 308 if (is == null) { 309 return null; 310 } 311 312 // read the policy 313 try { 314 serializationPolicy = SerializationPolicyLoader.loadFromStream(is, null); 315 } catch (ParseException e) { 316 LOG.error("ERROR: Failed to parse the policy file '" + m_serializationPolicyPath + "'", e); 317 } catch (IOException e) { 318 LOG.error("ERROR: Could not read the policy file '" + m_serializationPolicyPath + "'", e); 319 } finally { 320 try { 321 is.close(); 322 } catch (@SuppressWarnings("unused") IOException e) { 323 // Ignore this error 324 } 325 } 326 327 if (online) { 328 m_serPolicyOnline = serializationPolicy; 329 } else { 330 m_serPolicyOffline = serializationPolicy; 331 } 332 return serializationPolicy; 333 } 334 335 /** 336 * Removes a cached resource from the cache.<p> 337 * 338 * @param resource the resource 339 */ 340 private void uncacheResource(CmsResource resource) { 341 342 if (resource == null) { 343 return; 344 } 345 if ((m_serializationPolicyPath != null) && resource.getRootPath().equals(m_serializationPolicyPath)) { 346 m_serPolicyOffline = null; 347 } 348 } 349 350 /** 351 * Removes a bunch of cached resources from the cache.<p> 352 * 353 * @param resources a list of resources 354 * 355 * @see #uncacheResource(CmsResource) 356 */ 357 private void uncacheResources(List<CmsResource> resources) { 358 359 if (resources == null) { 360 return; 361 } 362 for (int i = 0, n = resources.size(); i < n; i++) { 363 // remove the resource 364 uncacheResource(resources.get(i)); 365 } 366 } 367 368}