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 service = this.services.get(anno.service());
100 else if (this.services.size() > 0) {
101 // Lookup remoting service
102 int count = 0;
103 for (Service s : this.services.values()) {
104 if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
105 service = s;
106 count++;
107 }
108 }
109 if (count == 1 && service != null)
110 log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
111 else
112 service = null;
113 }
114 if (service == null)
115 throw new RuntimeException("No service found for destination in class: " + clazz.getName());
116
117 // Channel reference.
118 List<String> channelIds = null;
119 if (anno.channels().length > 0)
120 channelIds = Arrays.asList(anno.channels());
121 else if (anno.channel().length() > 0)
122 channelIds = Collections.singletonList(anno.channel());
123 else if (this.channels.size() == 1) {
124 channelIds = new ArrayList<String>(this.channels.keySet());
125 log.info("Channel " + channelIds.get(0) + " selected for destination in class: " + clazz.getName());
126 }
127 else {
128 log.warn("No (or ambiguous) channel definition found for destination in class: " + clazz.getName());
129 channelIds = Collections.emptyList();
130 }
131
132 // Factory reference.
133 String factoryId = null;
134 if (anno.factory().length() > 0)
135 factoryId = anno.factory();
136 else if (this.factories.isEmpty()) {
137 props.put("scope", anno.scope());
138 props.put("source", clazz.getName());
139 log.info("Default POJO factory selected for destination in class: " + clazz.getName() + " with scope: " + anno.scope());
140 }
141 else if (this.factories.size() == 1) {
142 factoryId = this.factories.keySet().iterator().next();
143 log.info("Factory " + factoryId + " selected for destination in class: " + clazz.getName());
144 }
145 else
146 throw new RuntimeException("No (or ambiguous) factory definition found for destination in class: " + clazz.getName());
147
148 if (factoryId != null)
149 props.put("factory", factoryId);
150 if (!(anno.source().equals("")))
151 props.put("source", anno.source());
152
153 // Security roles.
154 List<String> roles = null;
155 if (anno.securityRoles().length > 0) {
156 roles = new ArrayList<String>(anno.securityRoles().length);
157 for (String role : anno.securityRoles())
158 roles.add(role);
159 }
160
161 Destination destination = new Destination(anno.id(), channelIds, props, roles, null, clazz);
162
163 service.getDestinations().put(destination.getId(), destination);
164 }
165 }
166
167 ///////////////////////////////////////////////////////////////////////////
168 // Static ServicesConfig loaders.
169
170 public ServicesConfig(InputStream customConfigIs, Configuration configuration, boolean scan) throws IOException, SAXException {
171 if (customConfigIs != null)
172 loadConfig(customConfigIs);
173
174 if (scan)
175 scan(configuration);
176 }
177
178 public void scan(Configuration configuration) {
179 List<ScannedItemHandler> handlers = new ArrayList<ScannedItemHandler>();
180 for (Factory factory : factories.values()) {
181 try {
182 Class<?> clazz = ClassUtil.forName(factory.getClassName());
183 Method method = clazz.getMethod("getScannedItemHandler");
184 if ((Modifier.STATIC & method.getModifiers()) != 0 && method.getParameterTypes().length == 0) {
185 ScannedItemHandler handler = (ScannedItemHandler)method.invoke(null);
186 handlers.add(handler);
187 }
188 else
189 log.warn("Factory class %s contains an unexpected signature for method: %s", factory.getClassName(), method);
190 }
191 catch (NoSuchMethodException e) {
192 // ignore
193 }
194 catch (ClassNotFoundException e) {
195 log.error(e, "Could not load factory class: %s", factory.getClassName());
196 }
197 catch (Exception e) {
198 log.error(e, "Error while calling %s.getScannedItemHandler() method", factory.getClassName());
199 }
200 }
201 scanConfig(configuration != null ? configuration.getFlexServicesConfigProperties() : null, handlers);
202 }
203
204 private void loadConfig(InputStream configIs) throws IOException, SAXException {
205 XMap doc = new XMap(configIs);
206 forElement(doc);
207 }
208
209 ///////////////////////////////////////////////////////////////////////////
210 // Services.
211
212 public Service findServiceById(String id) {
213 return services.get(id);
214 }
215
216 public List<Service> findServicesByMessageType(String messageType) {
217 List<Service> services = new ArrayList<Service>();
218 for (Service service : this.services.values()) {
219 if (messageType.equals(service.getMessageTypes()))
220 services.add(service);
221 }
222 return services;
223 }
224
225 public void addService(Service service) {
226 services.put(service.getId(), service);
227 }
228
229 ///////////////////////////////////////////////////////////////////////////
230 // Channels.
231
232 public Channel findChannelById(String id) {
233 return channels.get(id);
234 }
235
236 public void addChannel(Channel channel) {
237 channels.put(channel.getId(), channel);
238 }
239
240 ///////////////////////////////////////////////////////////////////////////
241 // Factories.
242
243 public Factory findFactoryById(String id) {
244 return factories.get(id);
245 }
246
247 public void addFactory(Factory factory) {
248 factories.put(factory.getId(), factory);
249 }
250
251 ///////////////////////////////////////////////////////////////////////////
252 // Destinations.
253
254 public Destination findDestinationById(String messageType, String id) {
255 for (Service service : services.values()) {
256 if (messageType == null || messageType.equals(service.getMessageTypes())) {
257 Destination destination = service.findDestinationById(id);
258 if (destination != null)
259 return destination;
260 }
261 }
262 return null;
263 }
264
265 public List<Destination> findDestinationsByMessageType(String messageType) {
266 List<Destination> destinations = new ArrayList<Destination>();
267 for (Service service : services.values()) {
268 if (messageType.equals(service.getMessageTypes()))
269 destinations.addAll(service.getDestinations().values());
270 }
271 return destinations;
272 }
273
274 ///////////////////////////////////////////////////////////////////////////
275 // Static helper.
276
277 private void forElement(XMap element) {
278 XMap services = element.getOne("services");
279 if (services != null) {
280 for (XMap service : services.getAll("service")) {
281 Service serv = Service.forElement(service);
282 this.services.put(serv.getId(), serv);
283 }
284
285 /* TODO: service-include...
286 for (Element service : (List<Element>)services.getChildren("service-include")) {
287 config.services.add(Service.forElement(service));
288 }
289 */
290 }
291
292 XMap channels = element.getOne("channels");
293 if (channels != null) {
294 for (XMap channel : channels.getAll("channel-definition")) {
295 Channel chan = Channel.forElement(channel);
296 this.channels.put(chan.getId(), chan);
297 }
298 }
299 else {
300 log.info("No channel definition found, using defaults");
301 EndPoint defaultEndpoint = new EndPoint("http://{server.name}:{server.port}/{context.root}/graniteamf/amf", "flex.messaging.endpoints.AMFEndpoint");
302 Channel defaultChannel = new Channel("my-graniteamf", "mx.messaging.channels.AMFChannel", defaultEndpoint, XMap.EMPTY_XMAP);
303 this.channels.put(defaultChannel.getId(), defaultChannel);
304 }
305
306 XMap factories = element.getOne("factories");
307 if (factories != null) {
308 for (XMap factory : factories.getAll("factory")) {
309 Factory fact = Factory.forElement(factory);
310 this.factories.put(fact.getId(), fact);
311 }
312 }
313 }
314
315
316 /**
317 * Remove service (new addings for osgi).
318 * @param clazz service class.
319 */
320 public void handleRemoveService(Class<?> clazz){
321 RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class);
322 if(anno!=null){
323 Service service=null;
324 if (anno.service().length() > 0){
325 service=services.get(anno.service());
326 }
327 else if (services.size() > 0) {
328 // Lookup remoting service
329 for (Service s : services.values()) {
330 if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
331 service = s;
332 log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
333 break;
334 }
335 }
336 }
337 if(service!=null){
338 Destination dest=service.getDestinations().remove(anno.id());
339 if (dest != null) {
340 dest.remove();
341 log.info("RemoteDestination:"+dest.getId()+" has been removed");
342 }
343 }else{
344 log.info("Service NOT Found!!");
345 }
346 }
347 }
348
349 }