001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bjalon 016 */ 017package org.nuxeo.ecm.mobile; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import javax.servlet.http.HttpServletRequest; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.common.utils.Path; 031import org.nuxeo.ecm.mobile.handler.RequestHandler; 032import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 033import org.nuxeo.runtime.api.Framework; 034import org.nuxeo.runtime.model.ComponentInstance; 035import org.nuxeo.runtime.model.DefaultComponent; 036 037/** 038 * @author <a href="mailto:bjalon@nuxeo.com">Benjamin JALON</a> 039 * @since 5.5 040 */ 041public class ApplicationRedirectServiceImpl extends DefaultComponent implements ApplicationDefinitionService { 042 043 private static final Log log = LogFactory.getLog(ApplicationRedirectServiceImpl.class); 044 045 private final Map<String, ApplicationDefinitionDescriptor> applications = new HashMap<String, ApplicationDefinitionDescriptor>(); 046 047 private final Map<String, RequestHandlerDescriptor> requestHandlers = new HashMap<String, RequestHandlerDescriptor>(); 048 049 private final List<ApplicationDefinitionDescriptor> applicationsOrdered = new ArrayList<ApplicationDefinitionDescriptor>(); 050 051 private List<String> unAuthenticatedURLPrefix; 052 053 private Path nuxeoRelativeContextPath; 054 055 public enum ExtensionPoint { 056 applicationDefinition, requestHandlers 057 } 058 059 protected String buildRedirectUrl(HttpServletRequest request, String... uris) { 060 Path path = new Path(""); 061 for (String uri : uris) { 062 path = path.append(uri); 063 } 064 065 return VirtualHostHelper.getBaseURL(request) + path.toString(); 066 } 067 068 protected Path getNuxeoRelativeContextPath() { 069 if (nuxeoRelativeContextPath == null) { 070 nuxeoRelativeContextPath = new Path(Framework.getProperty("org.nuxeo.ecm.contextPath")); 071 } 072 return nuxeoRelativeContextPath; 073 } 074 075 @Override 076 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 077 ExtensionPoint ep = Enum.valueOf(ExtensionPoint.class, extensionPoint); 078 switch (ep) { 079 case applicationDefinition: 080 registerApplication((ApplicationDefinitionDescriptor) contribution, contributor.getName().getName()); 081 break; 082 case requestHandlers: 083 registerRequestHandler((RequestHandlerDescriptor) contribution, contributor.getName().getName()); 084 break; 085 default: 086 throw new RuntimeException("error in exception handling configuration"); 087 } 088 089 } 090 091 protected void registerRequestHandler(RequestHandlerDescriptor rhd, String componentName) { 092 RequestHandlerDescriptor finalRH = null; 093 094 String requestHandlerName = rhd.getRequestHandlerName(); 095 096 if (requestHandlers.containsKey(requestHandlerName)) { 097 if (!rhd.disabled) { 098 String messageTemplate = "Request Handler definition %s will be" 099 + " overriden by on declared into %s component"; 100 String message = String.format(messageTemplate, requestHandlerName, componentName); 101 log.info(message); 102 } else { 103 String messageTemplate = "Request Handler definition '%s' will be removed as defined into %s"; 104 String message = String.format(messageTemplate, requestHandlerName, componentName); 105 log.info(message); 106 for (ApplicationDefinitionDescriptor app : applicationsOrdered) { 107 if (app.getRequestHandlerName().equals(requestHandlerName)) { 108 messageTemplate = "Request Handler definition '%s' used by %s Application Definition"; 109 message = String.format(messageTemplate, requestHandlerName, app.getName()); 110 log.warn(message); 111 } 112 } 113 } 114 finalRH = mergeRequestHandlerDescriptor(requestHandlers.get(requestHandlerName), rhd); 115 } else { 116 finalRH = rhd; 117 } 118 requestHandlers.put(requestHandlerName, finalRH); 119 120 } 121 122 private RequestHandlerDescriptor mergeRequestHandlerDescriptor(RequestHandlerDescriptor initial, 123 RequestHandlerDescriptor toMerge) { 124 if (toMerge.klass == null) { 125 toMerge.klass = initial.klass; 126 } 127 if (toMerge.properties == null) { 128 toMerge.properties = initial.properties; 129 } 130 return toMerge; 131 } 132 133 protected void registerApplication(ApplicationDefinitionDescriptor appDescriptor, String componentName) { 134 String name = appDescriptor.getName(); 135 136 validateApplicationDescriptor(appDescriptor, componentName); 137 138 if (applications.containsKey(name)) { 139 if (!appDescriptor.isDisable()) { 140 String messageTemplate = "Application definition '%s' will be overridden, " 141 + "replaced by ones declared into %s component"; 142 String message = String.format(messageTemplate, name, componentName); 143 log.info(message); 144 applicationsOrdered.remove(name); 145 } else { 146 String messageTemplate = "Application definition '%s' will be removed as defined into %s"; 147 String message = String.format(messageTemplate, name, componentName); 148 log.info(message); 149 disableApplication(name); 150 return; 151 } 152 } 153 if (appDescriptor.isDisable()) { 154 String messageTemplate = "Application definition '%s' already removed, definition into %s component ignored"; 155 String message = String.format(messageTemplate, name, componentName); 156 log.info(message); 157 disableApplication(name); 158 return; 159 } 160 161 String messageTemplate = "New Application definition detected '%s' into %s component"; 162 String message = String.format(messageTemplate, name, componentName); 163 log.info(message); 164 applications.put(name, appDescriptor); 165 applicationsOrdered.add(appDescriptor); 166 Collections.sort(applicationsOrdered, new MobileApplicationComparator()); 167 unAuthenticatedURLPrefix = null; 168 } 169 170 private void disableApplication(String applicationName) { 171 applications.remove(applicationName); 172 for (int i = 0; i < applicationsOrdered.size(); i++) { 173 ApplicationDefinitionDescriptor application = applicationsOrdered.get(i); 174 if (application.getName().equals(applicationName)) { 175 applicationsOrdered.remove(i); 176 } 177 } 178 } 179 180 protected String getBaseURL(HttpServletRequest request) { 181 return VirtualHostHelper.getWebAppName(request); 182 } 183 184 protected RequestHandlerDescriptor getRequestHandlerByName(String name) { 185 return requestHandlers.get(name); 186 } 187 188 private ApplicationDefinitionDescriptor getTargetApplication(HttpServletRequest request) { 189 190 for (ApplicationDefinitionDescriptor application : applicationsOrdered) { 191 RequestHandlerDescriptor rhd = getRequestHandlerByName(application.getRequestHandlerName()); 192 if (rhd == null) { 193 String message = "Can't find request handler %s for app definition %s, please check your configuration, skipping check"; 194 log.error(String.format(message, application.getRequestHandlerName(), application.getName())); 195 continue; 196 } 197 RequestHandler handler = rhd.getRequestHandlerInstance(); 198 199 if (handler.isRequestRedirectedToApplication(request)) { 200 String messageTemplate = "Request '%s' match the application '%s' request handler"; 201 String message = String.format(messageTemplate, request.getRequestURI(), application.getName()); 202 log.debug(message); 203 return application; 204 205 } 206 } 207 208 log.debug("Request match no application request handler"); 209 return null; 210 } 211 212 @Override 213 public String getApplicationBaseURL(HttpServletRequest request) { 214 ApplicationDefinitionDescriptor app = getTargetApplication(request); 215 if (app == null) { 216 log.debug(String.format("No application matched for this request," + " no Application base url found")); 217 return null; 218 } 219 return buildRedirectUrl(request, app.getApplicationRelativePath()); 220 } 221 222 @Override 223 public String getApplicationBaseURI(HttpServletRequest request) { 224 ApplicationDefinitionDescriptor app = getTargetApplication(request); 225 if (app == null) { 226 log.debug(String.format("No application matched for this request," + " no Application base uri found")); 227 return null; 228 } 229 230 return getNuxeoRelativeContextPath().append(app.getApplicationRelativePath()).toString(); 231 } 232 233 @Override 234 public String getLoginURL(HttpServletRequest request) { 235 ApplicationDefinitionDescriptor app = getTargetApplication(request); 236 if (app == null) { 237 log.debug(String.format("No application matched for this request," + " no Login page found")); 238 return null; 239 } 240 241 return buildRedirectUrl(request, app.getApplicationRelativePath(), app.getLoginPage()); 242 } 243 244 @Override 245 public String getLogoutURL(HttpServletRequest request) { 246 ApplicationDefinitionDescriptor app = getTargetApplication(request); 247 if (app == null) { 248 log.debug(String.format("No application matched for this request," + ", no Logout page found")); 249 return null; 250 } 251 return buildRedirectUrl(request, app.getApplicationRelativePath(), app.getLogoutPage()); 252 } 253 254 /** 255 * Check that application descriptor is valide and can be registred. Also modify path to if not well formed and log 256 * warn. 257 */ 258 private void validateApplicationDescriptor(ApplicationDefinitionDescriptor app, String componentName) { 259 if (app.getName() == null) { 260 String messageTemplate = "Application given in '%s' component is null, " + "can't register it"; 261 String message = String.format(messageTemplate, componentName); 262 throw new RuntimeException(message); 263 } 264 if (app.getApplicationRelativePath() == null) { 265 String messageTemplate = "Application name %s given in '%s' component as " 266 + "an empty base URL, can't register it"; 267 String message = String.format(messageTemplate, app.getName(), componentName); 268 throw new RuntimeException(message); 269 } 270 if (app.getApplicationRelativePath().startsWith("/")) { 271 log.warn("Application relative path must not start by a slash, please think" 272 + " to change your contribution"); 273 app.applicationRelativePath = app.getApplicationRelativePath().substring(1); 274 } 275 if (app.getApplicationRelativePath().endsWith("/")) { 276 log.warn("Application relative path must not end with a slash, please think" 277 + " to change your contribution"); 278 app.applicationRelativePath = app.getApplicationRelativePath().substring(0, 279 app.getApplicationRelativePath().length() - 1); 280 } 281 List<String> resourcesUriChanged = new ArrayList<String>(); 282 283 for (String resourceUri : app.getResourcesBaseUrl()) { 284 if (resourceUri.startsWith("/")) { 285 log.warn("Resource Uri relative path must not start by a slash, please" 286 + " think to change your contribution"); 287 resourceUri = resourceUri.substring(1); 288 } 289 resourcesUriChanged.add(resourceUri); 290 app.resourcesBaseUrl = resourcesUriChanged; 291 } 292 293 if (app.getLoginPage() == null) { 294 String messageTemplate = "Application name %s given in '%s' component as " 295 + "an empty login URL, can't register it"; 296 String message = String.format(messageTemplate, app.getName(), componentName); 297 throw new RuntimeException(message); 298 } 299 if (app.getLogoutPage() == null) { 300 String messageTemplate = "Application name %s given in '%s' component as " 301 + "an empty logout URL, can't register it"; 302 String message = String.format(messageTemplate, app.getName(), componentName); 303 throw new RuntimeException(message); 304 } 305 } 306 307 @Override 308 public List<String> getUnAuthenticatedURLPrefix(HttpServletRequest request) { 309 ApplicationDefinitionDescriptor app = getTargetApplication(request); 310 311 return getUnAuthenticatedURLPrefix(app); 312 } 313 314 private List<String> getUnAuthenticatedURLPrefix(ApplicationDefinitionDescriptor app) { 315 316 List<String> result = new ArrayList<String>(); 317 318 if (app == null) { 319 return null; 320 } 321 322 String loginPage = new Path(app.getApplicationRelativePath()).append(app.getLoginPage()).toString(); 323 log.debug("Add login page as Unauthenticated resources" + loginPage); 324 result.add(loginPage); 325 if (app.getResourcesBaseUrl() != null) { 326 result.addAll(app.getResourcesBaseUrl()); 327 } else { 328 log.error("No base URL found can't add unauthenticated URL for application: " + app.getName()); 329 } 330 331 return result; 332 } 333 334 @Override 335 public List<String> getUnAuthenticatedURLPrefix() { 336 if (unAuthenticatedURLPrefix == null) { 337 unAuthenticatedURLPrefix = new ArrayList<String>(); 338 339 for (ApplicationDefinitionDescriptor app : applicationsOrdered) { 340 unAuthenticatedURLPrefix.addAll(getUnAuthenticatedURLPrefix(app)); 341 } 342 } 343 return unAuthenticatedURLPrefix; 344 } 345 346 @Override 347 public boolean isResourceURL(HttpServletRequest request) { 348 ApplicationDefinitionDescriptor app = getTargetApplication(request); 349 if (app == null) { 350 return false; 351 } 352 List<String> resourcesBaseURL = app.getResourcesBaseUrl(); 353 354 if (resourcesBaseURL == null || resourcesBaseURL.size() == 0) { 355 return false; 356 } 357 String uri = request.getRequestURI(); 358 for (String resourceBaseURL : resourcesBaseURL) { 359 log.debug("Check if this is this Resources application : " 360 + new Path("/").append(getNuxeoRelativeContextPath()).append(resourceBaseURL) + " for uri : " + uri); 361 if (uri.startsWith(new Path("/").append(getNuxeoRelativeContextPath()).append(resourceBaseURL).toString())) { 362 return true; 363 } 364 } 365 return false; 366 } 367 368} 369 370class MobileApplicationComparator implements Comparator<ApplicationDefinitionDescriptor> { 371 372 private static final Log log = LogFactory.getLog(MobileApplicationComparator.class); 373 374 @Override 375 public int compare(ApplicationDefinitionDescriptor app1, ApplicationDefinitionDescriptor app2) { 376 if (app1.getOrder() == null) { 377 return 1; 378 } 379 if (app2.getOrder() == null) { 380 return -1; 381 } 382 if (app1.getOrder().equals(app2.getOrder())) { 383 log.warn("The two following applications have the same order," + " please change to have different order: " 384 + app1.getName() + " / " + app2.getName()); 385 } 386 return app1.getOrder().compareTo(app2.getOrder()); 387 388 } 389}