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