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    }