001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.config.flex; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033 034import org.granite.config.api.Configuration; 035import org.granite.logging.Logger; 036import org.granite.messaging.service.annotations.RemoteDestination; 037import org.granite.messaging.service.security.RemotingDestinationSecurizer; 038import org.granite.scan.ScannedItem; 039import org.granite.scan.ScannedItemHandler; 040import org.granite.scan.Scanner; 041import org.granite.scan.ScannerFactory; 042import org.granite.util.TypeUtil; 043import org.granite.util.XMap; 044import org.xml.sax.SAXException; 045 046import flex.messaging.messages.RemotingMessage; 047 048 049/** 050 * @author Franck WOLFF 051 */ 052public class ServicesConfig implements ScannedItemHandler { 053 054 /////////////////////////////////////////////////////////////////////////// 055 // Fields. 056 057 private static final Logger log = Logger.getLogger(ServicesConfig.class); 058 private static final String SERVICES_CONFIG_PROPERTIES = "META-INF/services-config.properties"; 059 060 private final Map<String, Service> services = new HashMap<String, Service>(); 061 private final Map<String, Channel> channels = new HashMap<String, Channel>(); 062 private final Map<String, Factory> factories = new HashMap<String, Factory>(); 063 064 065 /////////////////////////////////////////////////////////////////////////// 066 // Classpath scan initialization. 067 068 private void scanConfig(String serviceConfigProperties, List<ScannedItemHandler> handlers) { 069 Scanner scanner = ScannerFactory.createScanner(this, serviceConfigProperties != null ? serviceConfigProperties : SERVICES_CONFIG_PROPERTIES); 070 scanner.addHandlers(handlers); 071 try { 072 scanner.scan(); 073 } catch (Exception e) { 074 log.error(e, "Could not scan classpath for configuration"); 075 } 076 } 077 078 public boolean handleMarkerItem(ScannedItem item) { 079 return false; 080 } 081 082 public void handleScannedItem(ScannedItem item) { 083 if ("class".equals(item.getExtension()) && item.getName().indexOf('$') == -1) { 084 try { 085 handleClass(item.loadAsClass()); 086 } catch (Throwable t) { 087 log.error(t, "Could not load class: %s", item); 088 } 089 } 090 } 091 092 public void handleClass(Class<?> clazz) { 093 RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class); 094 if (anno != null && !("".equals(anno.id()))) { 095 XMap props = new XMap("properties"); 096 097 // Owning service. 098 Service service = null; 099 if (anno.service().length() > 0) { 100 log.info("Configuring service from RemoteDestination annotation: service=%s (class=%s, anno=%s)", anno.service(), clazz, anno); 101 service = this.services.get(anno.service()); 102 } 103 else if (this.services.size() > 0) { 104 // Lookup remoting service 105 log.info("Looking for service(s) with RemotingMessage type (class=%s, anno=%s)", clazz, anno); 106 int count = 0; 107 for (Service s : this.services.values()) { 108 if (RemotingMessage.class.getName().equals(s.getMessageTypes())) { 109 log.info("Found service with RemotingMessage type: service=%s (class=%s, anno=%s)", s, clazz, anno); 110 service = s; 111 count++; 112 } 113 } 114 if (count == 1 && service != null) 115 log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName()); 116 else { 117 log.error("Found %d matching services (should be exactly 1, class=%s, anno=%s)", count, clazz, anno); 118 service = null; 119 } 120 } 121 if (service == null) 122 throw new RuntimeException("No service found for destination in class: " + clazz.getName()); 123 124 // Channel reference. 125 List<String> channelIds = null; 126 if (anno.channels().length > 0) 127 channelIds = Arrays.asList(anno.channels()); 128 else if (anno.channel().length() > 0) 129 channelIds = Collections.singletonList(anno.channel()); 130 else if (this.channels.size() == 1) { 131 channelIds = new ArrayList<String>(this.channels.keySet()); 132 log.info("Channel " + channelIds.get(0) + " selected for destination in class: " + clazz.getName()); 133 } 134 else { 135 log.warn("No (or ambiguous) channel definition found for destination in class: " + clazz.getName()); 136 channelIds = Collections.emptyList(); 137 } 138 139 // Factory reference. 140 String factoryId = null; 141 if (anno.factory().length() > 0) 142 factoryId = anno.factory(); 143 else if (this.factories.isEmpty()) { 144 props.put("scope", anno.scope()); 145 props.put("source", clazz.getName()); 146 log.info("Default POJO factory selected for destination in class: " + clazz.getName() + " with scope: " + anno.scope()); 147 } 148 else if (this.factories.size() == 1) { 149 factoryId = this.factories.keySet().iterator().next(); 150 log.info("Factory " + factoryId + " selected for destination in class: " + clazz.getName()); 151 } 152 else 153 throw new RuntimeException("No (or ambiguous) factory definition found for destination in class: " + clazz.getName()); 154 155 if (factoryId != null) 156 props.put("factory", factoryId); 157 if (!(anno.source().equals(""))) 158 props.put("source", anno.source()); 159 160 // Security roles. 161 List<String> roles = null; 162 if (anno.securityRoles().length > 0) { 163 roles = new ArrayList<String>(anno.securityRoles().length); 164 for (String role : anno.securityRoles()) 165 roles.add(role); 166 } 167 168 // Securizer 169 if (anno.securizer() != RemotingDestinationSecurizer.class) 170 props.put("securizer", anno.securizer().getName()); 171 172 Destination destination = new Destination(anno.id(), channelIds, props, roles, null, clazz); 173 174 service.getDestinations().put(destination.getId(), destination); 175 } 176 } 177 178 /////////////////////////////////////////////////////////////////////////// 179 // Static ServicesConfig loaders. 180 181 public ServicesConfig(InputStream customConfigIs, Configuration configuration, boolean scan) throws IOException, SAXException { 182 if (customConfigIs != null) 183 loadConfig(customConfigIs); 184 185 if (scan) 186 scan(configuration); 187 } 188 189 public void scan(Configuration configuration) { 190 List<ScannedItemHandler> handlers = new ArrayList<ScannedItemHandler>(); 191 for (Factory factory : factories.values()) { 192 try { 193 Class<?> clazz = TypeUtil.forName(factory.getClassName()); 194 Method method = clazz.getMethod("getScannedItemHandler"); 195 if ((Modifier.STATIC & method.getModifiers()) != 0 && method.getParameterTypes().length == 0) { 196 ScannedItemHandler handler = (ScannedItemHandler)method.invoke(null); 197 handlers.add(handler); 198 } 199 else 200 log.warn("Factory class %s contains an unexpected signature for method: %s", factory.getClassName(), method); 201 } 202 catch (NoSuchMethodException e) { 203 // ignore 204 } 205 catch (ClassNotFoundException e) { 206 log.error(e, "Could not load factory class: %s", factory.getClassName()); 207 } 208 catch (Exception e) { 209 log.error(e, "Error while calling %s.getScannedItemHandler() method", factory.getClassName()); 210 } 211 } 212 scanConfig(configuration != null ? configuration.getFlexServicesConfigProperties() : null, handlers); 213 } 214 215 private void loadConfig(InputStream configIs) throws IOException, SAXException { 216 XMap doc = new XMap(configIs); 217 forElement(doc); 218 } 219 220 /////////////////////////////////////////////////////////////////////////// 221 // Services. 222 223 public Service findServiceById(String id) { 224 return services.get(id); 225 } 226 227 public List<Service> findServicesByMessageType(String messageType) { 228 List<Service> services = new ArrayList<Service>(); 229 for (Service service : this.services.values()) { 230 if (messageType.equals(service.getMessageTypes())) 231 services.add(service); 232 } 233 return services; 234 } 235 236 public void addService(Service service) { 237 services.put(service.getId(), service); 238 } 239 240 /////////////////////////////////////////////////////////////////////////// 241 // Channels. 242 243 public Channel findChannelById(String id) { 244 return channels.get(id); 245 } 246 247 public void addChannel(Channel channel) { 248 channels.put(channel.getId(), channel); 249 } 250 251 /////////////////////////////////////////////////////////////////////////// 252 // Factories. 253 254 public Factory findFactoryById(String id) { 255 return factories.get(id); 256 } 257 258 public void addFactory(Factory factory) { 259 factories.put(factory.getId(), factory); 260 } 261 262 /////////////////////////////////////////////////////////////////////////// 263 // Destinations. 264 265 public Destination findDestinationById(String messageType, String id) { 266 for (Service service : services.values()) { 267 if (messageType == null || messageType.equals(service.getMessageTypes())) { 268 Destination destination = service.findDestinationById(id); 269 if (destination != null) 270 return destination; 271 } 272 } 273 return null; 274 } 275 276 public List<Destination> findDestinationsByMessageType(String messageType) { 277 List<Destination> destinations = new ArrayList<Destination>(); 278 for (Service service : services.values()) { 279 if (messageType.equals(service.getMessageTypes())) 280 destinations.addAll(service.getDestinations().values()); 281 } 282 return destinations; 283 } 284 285 /////////////////////////////////////////////////////////////////////////// 286 // Static helper. 287 288 private void forElement(XMap element) { 289 XMap services = element.getOne("services"); 290 if (services != null) { 291 for (XMap service : services.getAll("service")) { 292 Service serv = Service.forElement(service); 293 this.services.put(serv.getId(), serv); 294 } 295 296 /* TODO: service-include... 297 for (Element service : (List<Element>)services.getChildren("service-include")) { 298 config.services.add(Service.forElement(service)); 299 } 300 */ 301 } 302 303 XMap channels = element.getOne("channels"); 304 if (channels != null) { 305 for (XMap channel : channels.getAll("channel-definition")) { 306 Channel chan = Channel.forElement(channel); 307 this.channels.put(chan.getId(), chan); 308 } 309 } 310 else { 311 log.info("No channel definition found, using defaults"); 312 EndPoint defaultEndpoint = new EndPoint("http://{server.name}:{server.port}/{context.root}/graniteamf/amf", "flex.messaging.endpoints.AMFEndpoint"); 313 Channel defaultChannel = new Channel("my-graniteamf", "mx.messaging.channels.AMFChannel", defaultEndpoint, XMap.EMPTY_XMAP); 314 this.channels.put(defaultChannel.getId(), defaultChannel); 315 } 316 317 XMap factories = element.getOne("factories"); 318 if (factories != null) { 319 for (XMap factory : factories.getAll("factory")) { 320 Factory fact = Factory.forElement(factory); 321 this.factories.put(fact.getId(), fact); 322 } 323 } 324 } 325 326 327 /** 328 * Remove service (new addings for osgi). 329 * @param clazz service class. 330 */ 331 public void handleRemoveService(Class<?> clazz){ 332 RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class); 333 if(anno!=null){ 334 Service service=null; 335 if (anno.service().length() > 0){ 336 service=services.get(anno.service()); 337 } 338 else if (services.size() > 0) { 339 // Lookup remoting service 340 for (Service s : services.values()) { 341 if (RemotingMessage.class.getName().equals(s.getMessageTypes())) { 342 service = s; 343 log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName()); 344 break; 345 } 346 } 347 } 348 if(service!=null){ 349 Destination dest=service.getDestinations().remove(anno.id()); 350 if (dest != null) { 351 dest.remove(); 352 log.info("RemoteDestination:"+dest.getId()+" has been removed"); 353 } 354 }else{ 355 log.info("Service NOT Found!!"); 356 } 357 } 358 } 359 360}