001    /*
002      Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com>
003    
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public
015      License along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
017      USA */
018    
019    package org.objectweb.jac.core;
020    
021    
022    import java.io.Serializable;
023    import java.lang.reflect.Array;
024    import java.net.URL;
025    import org.apache.log4j.Logger;
026    import org.objectweb.jac.core.rtti.*;
027    import org.objectweb.jac.util.Classes;
028    import org.objectweb.jac.util.Strings;
029    
030    /**
031     * This class defines aspect component configurations so that the
032     * programmer or the system administrator will be able to configure
033     * the available aspects for a given application.
034     *
035     * <p>The default available aspects in the system are the ones that
036     * are declared to the AC manager in the <code>jac.prop</code> (see
037     * the <code>org.objectweb.jac.acs</code> property).
038     *
039     * @see ApplicationRepository
040     * @see Application
041     * @see Parser
042     * @see ACManager
043     */
044    
045    public class ACConfiguration implements Serializable {
046        static Logger logger = Logger.getLogger("jac");
047        static Logger loggerAspects = Logger.getLogger("aspects");
048        static Logger loggerConf = Logger.getLogger("aspects.config");
049        static Logger loggerPerf = Logger.getLogger("perf");
050    
051        /** The application this configuration belongs to. */
052        protected Application application;
053        /** The name of the configured AC. */
054        protected String name;
055        /** The aspect component that corresponds to this configuration
056            once instantiated (null before). This is a local aspect
057            component. */
058        transient AspectComponent instance = null;
059       
060        /** The configuration file's URL. */
061        protected URL filePath;
062    
063        String acPath = null;
064    
065        /**
066         * Sets the URL of the configuration file that defines the
067         * configuration operations.
068         *
069         * @param filePath a valid file path
070         * @see #getURL() */
071    
072        public void setURL(URL filePath) {
073            this.filePath = filePath;
074        }
075    
076        /**
077         * The getter of the configuration file's URL.
078         *
079         * @return the URL
080         * @see #setURL(URL) */
081    
082        public URL getURL() {
083            return filePath;
084        }
085    
086        /**
087         * This flag tells if the aspect that is configured by the current
088         * configuration will be woven on demand (by the administrator or
089         * by a configuration program) or if the aspect will be
090         * automatically woven and restored by the system.
091         *
092         * <p>For instance, a persistence aspect should always have this
093         * configuration flag to false whilst a debugging aspect should
094         * most of the time be woven on demand (when debugging is
095         * needed). */
096    
097        protected boolean weaveOnDemand = true;
098       
099        /**
100         * Gets the <code>weaveOnDemand</code> flag value.
101         * @return the flag value */
102    
103        public boolean getWeaveOnDemand() {
104            return weaveOnDemand;
105        }
106       
107        /**
108         * Sets the <code>weaveOnDemand</code> flag value.
109         * @param b the new flag value */
110    
111        public void setWeaveOnDemand(boolean b) {
112            weaveOnDemand = b;
113        }
114    
115        /**
116         * Creates a new aspect component configuration.
117         *
118         * @param application the application this configuration belongs to 
119         * @param name the name of the AC as defined in the declared ACs of
120         * the AC manager, or the of the aspect component's class
121         * @param filePath the path of the configuration file; it can be
122         * absolute but, if relative, it is automatically concatened to the
123         * application's path
124         * @param weaveNow a true value means that the aspect that
125         * configured by this configuration will be automatically woven at
126         * the application's start, a false value means that the user will
127         * have to weave it with a program (or with the administration
128         * GUI); default is true */
129    
130        public ACConfiguration(Application application, String name, 
131                               String filePath, boolean weaveNow) {
132            this.name = name;
133            this.application = application;
134            this.weaveOnDemand = ! weaveNow;
135            if (filePath!=null) {
136                try {
137                    if (filePath.startsWith("file:")) {
138                        filePath = filePath.substring(5);
139                    }
140                    this.filePath = new URL("file:"+filePath);
141                } catch (Exception e) {
142                    e.printStackTrace();
143                }
144            }
145    
146            // If name is not the name of a registered AC, 
147            // It must be the class name of the AC
148            ACManager acm = ACManager.getACM();
149            if (!acm.isACDeclared(name)) {
150                acm.declareAC(name,name);
151            }
152        }
153    
154        /**
155         * Gets the name of the configured AC as defined in the declared
156         * ACs of the AC manager.
157         *
158         * @return the AC name
159         * @see #setName(String)
160         * @see ACManager */
161    
162        public String getName() {
163            return name;
164        }
165    
166        /**
167         * The aspect name setter. Must be declared in the
168         * <code>jac.prop</code>.
169         *
170         * @param name the aspect name 
171         * @see ACManager */
172    
173        public void setName(String name) { 
174            this.name = name;
175        }
176       
177        /**
178         * Gets the aspect component instance that corresponds to this
179         * configuration.
180         *
181         * @return the AC instance */
182    
183        public AspectComponent getInstance() {
184            return instance;
185        }
186    
187        /**
188         * Gets the owning application.
189         *
190         * @return the application that owns this configuration */
191    
192        public Application getApplication() {
193            return application;
194        }
195    
196        /**
197         * Return the class item for the configured aspect component.
198         *
199         * @return the AC type */
200    
201        public ClassItem getAspectClass() {
202            String acPath = ((ACManager)ACManager.get()).getACPathFromName(name);
203            return ClassRepository.get().getClass(acPath);
204        }
205    
206        /**
207         * Instantiates a new aspect component.
208         *
209         * @return the new aspect component, null if something went wrong
210         */
211    
212        protected AspectComponent instantiate() {
213            loggerAspects.debug("instantiating "+name);
214            ACManager acm = (ACManager)ACManager.get(); 
215            //      String oldAC = Collaboration.get().getCurAC();
216            //      Collaboration.get().setCurAC(
217            //         getApplication().getName() + "." + name );
218            try {
219                instance = (AspectComponent) acm.getObject(
220                    application.getName()+"."+name);
221                if (instance == null) {
222                    if (acPath == null)
223                        acPath = acm.getACPathFromName(name);
224                    loggerAspects.debug(name + " : " + acPath);
225                    if( acPath == null ) return null;
226                    Class cl = Class.forName( acPath );
227                    loggerAspects.debug("instantiating "+cl);
228                    instance = (AspectComponent) cl.newInstance();
229                    instance.setApplication(getApplication().getName());
230                }
231                return instance;
232            } catch(Exception e) {
233                e.printStackTrace();
234                return null;
235            } finally {
236                //         Collaboration.get().setCurAC(oldAC);
237            }
238        }
239    
240        /**
241         * Configures the aspect component.
242         *
243         * <p>This method takes the aspect component instance that corresponds
244         * to this configuration, parse the configuration file, and calls
245         * all the configuration operations that are defined in this file.
246         *
247         * @see Parser
248         * @see #getInstance() */
249    
250    
251        protected void configure() {
252            logger.info("--- configuring "+name+" aspect ---");
253            long start = System.currentTimeMillis();
254            String[] defaults = instance.getDefaultConfigs();
255            for (int i=0; i<defaults.length; i++) {
256                instance.configure(name,defaults[i]);
257            }
258            instance.configure(name,filePath.getFile());
259            instance.whenConfigured();
260            loggerPerf.info("aspect "+name+" configured in "+
261                            (System.currentTimeMillis()-start)+"ms");
262        }
263    
264        public static Object convertArray(Object[] array, Class componentType, Imports imports) 
265            throws Exception 
266        {
267            Object result  = Array.newInstance(componentType, array.length);
268            for (int i=0; i<array.length; i++) {
269                Array.set(result,i,convertValue(array[i],componentType,imports));
270            }
271            return result;
272        }
273    
274        public static Object convertValue(Object object, Class type) 
275            throws Exception 
276        {
277            return convertValue(object,type,null);
278        }
279    
280        public static Object convertValue(Object object, Class type, Imports imports) 
281            throws Exception 
282        {
283            Object result = object;
284            if (object != null && object.getClass() != type) {
285                try {
286                    if (type.isArray()) {
287                        result = convertArray((Object[])object,type.getComponentType(),imports);
288                    } else if (type==double.class || type==Double.class) {
289                        result = new Double((String)object);
290                    } else if (type==int.class || type==Integer.class) {
291                        result = new Integer((String)object);
292                    } else if (type==long.class || type==Long.class) {
293                        result = new Long((String)object);
294                    } else if (type==float.class || type==Float.class) {
295                        result = new Float((String)object);
296                    } else if (type==boolean.class || type==Boolean.class) {
297                        result = Boolean.valueOf((String)object);
298                    } else if (type==short.class || type==Short.class) {
299                        result = new Short((String)object);
300                    } else if (type==Class.class) {
301                        result = Class.forName((String)object);
302                    } else if (type==ClassItem.class) {
303                        result = getClass((String)object,imports);
304                    } else if (type==VirtualClassItem.class) { 
305                        result = ClassRepository.get().getVirtualClassStrict((String)object);
306                    } else if  (AbstractMethodItem.class.isAssignableFrom(type)) {
307                        String str = (String)object;
308                        int index = str.indexOf("(");
309                        try {
310                            ClassItem classItem;
311                            if (index==-1) {
312                                classItem = getClass(str,imports);
313                                result = classItem.getConstructor("");
314                            } else {
315                                classItem = getClass(
316                                    str.substring(0,index),imports);
317    
318                                // resolve parameter types
319                                String[] paramTypes = 
320                                    Strings.split(str.substring(index+1,str.length()-1), ",");
321                                resolveTypes(paramTypes,imports);
322                                String fullName = str.substring(0,index+1)+Strings.join(paramTypes,",")+")";
323                                result = classItem.getConstructor(fullName.substring(index));
324                            }
325                        } catch (NoSuchClassException e) {
326                            if (index!=-1) {
327                                // resolve parameter types
328                                String[] paramTypes = 
329                                    Strings.split(str.substring(index+1,str.length()-1), ",");
330                                resolveTypes(paramTypes,imports);
331                                str = str.substring(0,index+1)+Strings.join(paramTypes,",")+")";
332                                index = str.lastIndexOf(".",index);
333                            } else {
334                                index = str.lastIndexOf(".");
335                            }
336                            if (index!=-1) {
337                                try {
338                                    ClassItem classItem = getClass(
339                                        str.substring(0,index),imports);
340                                    result = classItem.getAbstractMethod(
341                                        str.substring(index+1));
342                                } catch (NoSuchClassException e2) {
343                                    throw new Exception("Failed to convert "+str+
344                                                        " into a "+type.getName());
345                                }
346                            } else {
347                                throw new Exception("Failed to convert "+str+
348                                                    " into a "+type.getName());
349                            }
350                        }
351                    } else if (FieldItem.class.isAssignableFrom(type)) {
352                        String str = (String)object;
353                        loggerConf.debug("Trying to convert "+str+" into a FieldItem");
354                        int index = str.length();
355                        result = null; 
356                        while (index!=-1 && result==null) {
357                            index = str.lastIndexOf(".",index-1);
358                            if (index!=-1) {
359                                try {
360                                    loggerConf.debug(
361                                        "  Trying class="+str.substring(0,index)+
362                                        " and field="+str.substring(index+1));
363                                    ClassItem classItem = getClass(
364                                        str.substring(0,index),imports);
365                                    result = classItem.getField(str.substring(index+1));
366                                    loggerConf.debug("    -> "+result);
367                                } catch (NoSuchClassException e) {
368                                    loggerConf.info(
369                                        "  Failed conversion of "+object+
370                                        " to FieldItem with class="+str.substring(0,index)+
371                                        " and field="+str.substring(index+1));
372                                }
373                            }
374                        }
375                        if (index==-1 || result==null) {
376                            throw new Exception("Failed to convert "+str+
377                                                " into a "+type.getName());
378                        }
379                    } else if (MemberItem.class.isAssignableFrom(type)) {
380                        String str = (String)object;
381                        int index = -1;
382                        int paren = str.indexOf("(");
383                        if (paren==-1)
384                            index = str.length();
385                        else 
386                            index = paren;
387    
388                        result = null; 
389                        while (index!=-1 && result==null) {
390                            index = str.lastIndexOf(".",index-1);
391                            if (index!=-1) {
392                                try {
393                                    ClassItem classItem = getClass(
394                                        str.substring(0,index),imports);
395                                    result = classItem.getMember(str.substring(index+1));
396                                } catch (NoSuchClassException e) {
397                                }
398                            }
399                        }
400    
401                        if (index==-1 || result==null) {
402                            throw new Exception("Failed to convert "+str+
403                                                " into a "+type.getName());
404                        }
405                    } else {
406                        throw new Exception("Don't know how to convert "+object+" into a "+type);
407                    }
408                } catch (Exception e) {
409                    loggerConf.info("Failed to convert "+object+" into "+type.getName(),e);
410                    throw new Exception(
411                        "Failed to convert "+object+" into "+type.getName()+" : "+e);
412                }
413            }
414            loggerConf.debug("Converted "+object+" into "+type+": "+result);
415            return result;
416        }
417    
418        protected static void resolveTypes(String[] types, Imports imports) {
419            for (int i=0; i<types.length; i++) {
420                String type = types[i];
421                boolean isArray = false;
422                if (type.endsWith("[]")) {
423                    type = type.substring(0,type.length()-2); 
424                }
425                try {
426                    if (isArray)
427                        types[i] = imports.getClass(types[i].substring(0,types[i].length()-2)).getName()+"[]";
428                    else
429                        types[i] = imports.getClass(types[i]).getName();
430                } catch (NoSuchClassException nse) {
431                    if (!Classes.isPrimitiveType(type))
432                        throw nse;
433                }
434            }
435        }
436    
437        protected static ClassItem getClass(String name, Imports imports) {
438            if (imports!=null)
439                return imports.getClass(name);
440            else
441                return ClassRepository.get().getClass(name);
442        }
443       
444        /**
445         * Instantiates, configures, and weaves the aspect component that
446         * corresponds to this configuration.
447         *
448         * @see #instantiate()
449         * @see #configure() 
450         */
451        public void weave() {
452            //acs.put( acName, ac );
453            instantiate();
454            if (instance == null) {
455                logger.error("could not instantiate aspect "+name);
456                return;
457            }
458            try {
459                instance.beforeConfiguration();
460                configure();
461            } catch (Exception e) {
462                e.printStackTrace();
463                return;
464            }
465            loggerAspects.debug(
466                "registering " + instance + 
467                " on application " + getApplication() );
468            // This line is useful for composite aspects
469            instance.doRegister();
470            ACManager.get().register( getApplication().getName() + 
471                                      "." + name, instance );
472        }
473    
474        /**
475         * Unweaves the aspect component that corresponds to this
476         * configuration.
477         */
478        public void unweave() {
479            // This line is useful for composite aspects
480            instance.doUnregister();
481            ((ACManager)ACManager.get()).unregister( application.getName() + "." + name );
482            instance = null;
483        }   
484    
485        /**
486         * Returns a string representation of this configuration.
487         * @return a string 
488         */
489        public String toString() {
490            return "AC " + name + " configuration";   
491        }
492    
493    }