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    }