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