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
021 package org.granite.config.flex;
022
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.lang.reflect.Method;
026 import java.lang.reflect.Modifier;
027 import java.util.ArrayList;
028 import java.util.Arrays;
029 import java.util.Collections;
030 import java.util.HashMap;
031 import java.util.List;
032 import java.util.Map;
033
034 import org.granite.config.api.Configuration;
035 import org.granite.logging.Logger;
036 import org.granite.messaging.service.annotations.RemoteDestination;
037 import org.granite.messaging.service.security.RemotingDestinationSecurizer;
038 import org.granite.scan.ScannedItem;
039 import org.granite.scan.ScannedItemHandler;
040 import org.granite.scan.Scanner;
041 import org.granite.scan.ScannerFactory;
042 import org.granite.util.TypeUtil;
043 import org.granite.util.XMap;
044 import org.xml.sax.SAXException;
045
046 import flex.messaging.messages.RemotingMessage;
047
048
049 /**
050 * @author Franck WOLFF
051 */
052 public 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 }