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    }