001/* 002 * (C) Copyright 2006-2017 Nuxeo (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 * bstefanescu 018 */ 019package org.nuxeo.ecm.webengine.app; 020 021import java.io.BufferedReader; 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.lang.reflect.Type; 027import java.net.URL; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.HashSet; 032import java.util.Map; 033import java.util.Set; 034 035import javax.servlet.http.HttpServletRequest; 036import javax.servlet.http.HttpServletResponse; 037import javax.ws.rs.Path; 038import javax.ws.rs.core.Application; 039import javax.ws.rs.core.Context; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.webengine.WebEngine; 044import org.nuxeo.ecm.webengine.jaxrs.ApplicationFactory; 045import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext; 046import org.nuxeo.ecm.webengine.jaxrs.scan.Scanner; 047import org.nuxeo.ecm.webengine.loader.WebLoader; 048import org.nuxeo.ecm.webengine.model.Module; 049import org.nuxeo.ecm.webengine.model.WebContext; 050import org.nuxeo.ecm.webengine.model.WebObject; 051import org.nuxeo.ecm.webengine.model.impl.DefaultTypeLoader; 052import org.nuxeo.ecm.webengine.model.impl.ModuleConfiguration; 053import org.nuxeo.ecm.webengine.model.impl.ModuleManager; 054import org.osgi.framework.Bundle; 055 056import com.sun.jersey.core.spi.component.ComponentContext; 057import com.sun.jersey.core.spi.component.ComponentScope; 058import com.sun.jersey.server.impl.inject.ServerInjectableProviderContext; 059import com.sun.jersey.spi.inject.Injectable; 060import com.sun.jersey.spi.inject.InjectableProvider; 061 062/** 063 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 064 */ 065public class WebEngineModule extends Application implements ApplicationFactory { 066 067 private final static Log log = LogFactory.getLog(WebEngineModule.class); 068 069 public final static String WEBOBJECT_ANNO = "Lorg/nuxeo/ecm/webengine/model/WebObject;"; 070 071 public final static String WEBADAPTER_ANNO = "Lorg/nuxeo/ecm/webengine/model/WebAdapter;"; 072 073 protected ServerInjectableProviderContext context; 074 075 protected Bundle bundle; 076 077 protected ModuleConfiguration cfg; 078 079 void init(WebEngine engine, Bundle bundle, File configuration, Map<String, String> attrs) 080 throws ReflectiveOperationException, IOException { 081 this.bundle = bundle; 082 loadModuleConfigurationFile(engine, configuration); 083 if (attrs != null) { 084 String v = attrs.get("name"); 085 if (v != null) { 086 cfg.name = v; 087 } 088 v = attrs.get("extends"); 089 if (v != null) { 090 cfg.base = v; 091 } 092 v = attrs.get("headless"); 093 if (v != null) { 094 cfg.isHeadless = Boolean.parseBoolean(v); 095 } 096 } 097 if (cfg.name == null) { 098 throw new IllegalStateException("No name given for web module in bundle " + bundle.getSymbolicName()); 099 } 100 initTypes(bundle, attrs.get("package"), engine); 101 } 102 103 private void initTypes(Bundle bundle, String packageBase, WebEngine engine) 104 throws ReflectiveOperationException, IOException { 105 cfg.types = getWebTypes(); 106 if (cfg.types == null) { 107 // try the META-INF/web-types file 108 loadMetaTypeFile(engine); 109 if (cfg.types == null) { 110 // try scanning the bundle 111 scan(bundle, packageBase); 112 if (cfg.types == null) { 113 throw new IllegalStateException("No web types defined in web module " + cfg.name + " from bundle " 114 + bundle.getSymbolicName()); 115 } 116 } else { 117 initRoots(engine); 118 } 119 } else { 120 initRoots(engine); 121 } 122 } 123 124 private void scan(Bundle bundle, String packageBase) throws ReflectiveOperationException, IOException { 125 if (packageBase == null) { 126 packageBase = "/"; 127 } 128 Scanner scanner = new Scanner(bundle, packageBase, Scanner.PATH_ANNO, Scanner.PROVIDER_ANNO, WEBOBJECT_ANNO, 129 WEBADAPTER_ANNO); 130 scanner.scan(); 131 Collection<Class<?>> paths = scanner.getCollector(Scanner.PATH_ANNO); 132 Collection<Class<?>> providers = scanner.getCollector(Scanner.PROVIDER_ANNO); 133 cfg.roots = new Class<?>[paths.size() + providers.size()]; 134 int i = 0; 135 for (Class<?> cl : paths) { 136 cfg.roots[i++] = cl; 137 } 138 for (Class<?> cl : providers) { 139 cfg.roots[i++] = cl; 140 } 141 Collection<Class<?>> objs = scanner.getCollector(WEBOBJECT_ANNO); 142 Collection<Class<?>> adapters = scanner.getCollector(WEBADAPTER_ANNO); 143 cfg.types = new Class<?>[objs.size() + adapters.size()]; 144 i = 0; 145 for (Class<?> cl : objs) { 146 cfg.types[i++] = cl; 147 } 148 for (Class<?> cl : adapters) { 149 cfg.types[i++] = cl; 150 } 151 } 152 153 private void loadMetaTypeFile(WebEngine engine) throws ReflectiveOperationException, IOException { 154 URL url = bundle.getEntry(DefaultTypeLoader.WEB_TYPES_FILE); 155 if (url != null) { 156 try (InputStream in = url.openStream()) { 157 cfg.types = readWebTypes(engine.getWebLoader(), in); 158 } 159 } 160 } 161 162 private static Class<?>[] readWebTypes(WebLoader loader, InputStream in) 163 throws ReflectiveOperationException, IOException { 164 HashSet<Class<?>> types = new HashSet<>(); 165 BufferedReader reader = null; 166 try { 167 reader = new BufferedReader(new InputStreamReader(in)); 168 String line; 169 while ((line = reader.readLine()) != null) { 170 line = line.trim(); 171 if (line.length() == 0 || line.startsWith("#")) { 172 continue; 173 } 174 int p = line.indexOf('|'); 175 if (p > -1) { 176 line = line.substring(0, p); 177 } 178 Class<?> cl = loader.loadClass(line); 179 types.add(cl); 180 } 181 } finally { 182 if (reader != null) { 183 try { 184 reader.close(); 185 } catch (IOException e) { 186 } 187 } 188 } 189 return types.toArray(new Class<?>[types.size()]); 190 } 191 192 private void initRoots(WebEngine engine) { 193 ArrayList<Class<?>> roots = new ArrayList<>(); 194 for (Class<?> cl : cfg.types) { 195 if (cl.isAnnotationPresent(Path.class)) { 196 roots.add(cl); 197 } else if (cfg.rootType != null) { 198 // compat mode - should be removed later 199 WebObject wo = cl.getAnnotation(WebObject.class); 200 if (wo != null && wo.type().equals(cfg.rootType)) { 201 log.warn("Invalid web module " + cfg.name + " from bundle " + bundle.getSymbolicName() 202 + ". The root-type " + cl 203 + " in module.xml is deprecated. Consider using @Path annotation on you root web objects."); 204 } 205 } 206 } 207 if (roots.isEmpty()) { 208 log.error( 209 "No root web objects found in web module " + cfg.name + " from bundle " + bundle.getSymbolicName()); 210 // throw new 211 // IllegalStateException("No root web objects found in web module "+cfg.name+" from bundle 212 // "+bundle.getSymbolicName()); 213 } 214 cfg.roots = roots.toArray(new Class<?>[roots.size()]); 215 } 216 217 private ModuleConfiguration loadModuleConfigurationFile(WebEngine engine, File file) throws IOException { 218 if (file != null && file.isFile()) { 219 cfg = ModuleManager.readConfiguration(engine, file); 220 } else { 221 cfg = new ModuleConfiguration(); 222 } 223 cfg.engine = engine; 224 cfg.file = file; 225 return cfg; 226 } 227 228 public ModuleConfiguration getConfiguration() { 229 return cfg; 230 } 231 232 public Module getModule(WebContext context) { 233 return cfg.get(context); 234 } 235 236 public Bundle getBundle() { 237 return bundle; 238 } 239 240 @Override 241 public Set<Class<?>> getClasses() { 242 if (cfg.roots == null) { 243 return new HashSet<>(); 244 } 245 Set<Class<?>> set = new HashSet<>(); 246 Collections.addAll(set, cfg.roots); 247 return set; 248 } 249 250 @Override 251 public Set<Object> getSingletons() { 252 Set<Object> set = new HashSet<>(); 253 set.add(new HttpServletRequestProvider()); 254 set.add(new HttpServletResponseProvider()); 255 return set; 256 } 257 258 public String getId() { 259 return bundle.getSymbolicName(); 260 } 261 262 public Class<?>[] getWebTypes() { 263 return null; // types will be discovered 264 } 265 266 @Override 267 public Application getApplication(Bundle bundle, Map<String, String> args) 268 throws ReflectiveOperationException, IOException { 269 return WebEngineModuleFactory.getApplication(this, bundle, args); 270 } 271 272 public static class HttpServletRequestProvider 273 implements InjectableProvider<Context, Type>, Injectable<HttpServletRequest> { 274 @Override 275 public HttpServletRequest getValue() { 276 RequestContext ctx = RequestContext.getActiveContext(); 277 return ctx != null ? ctx.getRequest() : null; 278 } 279 280 @Override 281 public ComponentScope getScope() { 282 return ComponentScope.PerRequest; 283 } 284 285 @Override 286 public Injectable<HttpServletRequest> getInjectable(ComponentContext ic, Context a, Type c) { 287 if (!c.equals(HttpServletRequest.class)) { 288 return null; 289 } 290 return this; 291 } 292 293 } 294 295 public static class HttpServletResponseProvider 296 implements InjectableProvider<Context, Type>, Injectable<HttpServletResponse> { 297 @Override 298 public HttpServletResponse getValue() { 299 RequestContext ctx = RequestContext.getActiveContext(); 300 return ctx != null ? ctx.getResponse() : null; 301 } 302 303 @Override 304 public ComponentScope getScope() { 305 return ComponentScope.PerRequest; 306 } 307 308 @Override 309 public Injectable<HttpServletResponse> getInjectable(ComponentContext ic, Context a, Type c) { 310 if (!c.equals(HttpServletResponse.class)) { 311 return null; 312 } 313 return this; 314 } 315 316 } 317 318}