View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.objectfactory;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import org.apache.commons.lang.StringUtils;
38  import org.apache.commons.lang.exception.ExceptionUtils;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  import java.lang.reflect.Modifier;
43  import java.util.HashMap;
44  import java.util.Map;
45  import java.util.Properties;
46  
47  /**
48   * This {@link info.magnolia.objectfactory.ComponentProvider} is using the configuration provided by
49   * {@link info.magnolia.cms.core.SystemProperty}. Each property key is the interface/base-class, and the value
50   * is either the implementation-to-use class name, an implementation of {@link info.magnolia.objectfactory.ComponentFactory}
51   * which is used to instantiate the desired implementation, or the path to a node in the repository (in the form of
52   * <code>repository:/path/to/node</code> or <code>/path/to/node</code>, which defaults to the <code>config</code>
53   * repository). In the latter case, the component is constructed via {@link info.magnolia.objectfactory.ObservedComponentFactory}
54   * and reflects (through observation) the contents of the given path.
55   *
56   * @author Philipp Bracher
57   * @version $Revision: 25238 $ ($Author: pbaerfuss $)
58   */
59  public class DefaultComponentProvider implements ComponentProvider {
60      private final static Logger log = LoggerFactory.getLogger(DefaultComponentProvider.class);
61  
62      /**
63       * Registered singleton instances.
64       */
65      private final Map<Class<?>, Object> instances = new HashMap<Class<?>, Object>();
66  
67      /**
68       * Registered prototypes used by newInstance().
69       */
70      private final Map<Class<?>, ComponentFactory<?>> factories = new HashMap<Class<?>, ComponentFactory<?>>();
71  
72      private final Properties mappings;
73  
74      DefaultComponentProvider(Properties mappings) {
75          // Ideally, the dependency should be on SystemProperty or other relevant object.
76          // Hopefully, we'll de-staticize SystemProperty soon.
77          this.mappings = mappings;
78  
79          // TODO : we have a dependency on ClassFactory, but we can't inject it here,
80          // since it might get swapped later
81      }
82  
83      public synchronized <T> T getSingleton(Class<T> type) {
84          T instance = (T) instances.get(type);
85          if (instance == null) {
86              log.debug("No instance for {} yet, creating new one.", type);
87              instance = newInstance(type);
88              instances.put(type, instance);
89              log.debug("New instance for {} created: {}", type, instance);
90          }
91  
92          return instance;
93      }
94  
95      /**
96       * Creates a new instance of the passed interface / class by using the registered implementation.
97       * If this fails a {@link IllegalStateException} is thrown.
98       *
99       * @throws IllegalStateException
100      */
101     public <T> T newInstance(Class<T> type) {
102         if (type == null) {
103             log.error("type can't be null", new Throwable());
104             return null;
105         }
106 
107         try {
108             if (factories.containsKey(type)) {
109                 final ComponentFactory<T> factory = (ComponentFactory<T>) factories.get(type);
110                 return factory.newInstance();
111             }
112 
113             final String className = getImplementationName(type);
114             if (isInRepositoryDefinition(className)) {
115                 String repository = ContentRepository.CONFIG;
116                 String path = className;
117                 if (className.indexOf(':') >= 0) {
118                     repository = StringUtils.substringBefore(className, ":");
119                     path = StringUtils.substringAfter(className, ":");
120                 }
121                 final ObservedComponentFactory<T> factory = new ObservedComponentFactory<T>(repository, path, type);
122                 setInstanceFactory(type, factory);
123                 // now that the factory is registered, we call ourself again
124                 return newInstance(type);
125             } else {
126                 final Class<?> clazz = Classes.getClassFactory().forName(className);
127                 if (!isConcrete(clazz)) {
128                     throw new MgnlInstantiationException("No concrete implementation defined for " + clazz); 
129                 }
130                 final Object instance = Classes.getClassFactory().newInstance(clazz);
131 
132                 if (instance instanceof ComponentFactory) {
133                     final ComponentFactory<T> factory = (ComponentFactory<T>) instance;
134                     setInstanceFactory(type, factory);
135                     return factory.newInstance();
136                 }
137                 return (T) instance;
138             }
139         } catch (Exception e) {
140             if (e instanceof MgnlInstantiationException) {
141                 throw (MgnlInstantiationException) e;
142             }
143             throw new MgnlInstantiationException("Can't instantiate an implementation of this class [" + type.getName() + "]: " + ExceptionUtils.getMessage(e), e);
144         }
145     }
146 
147     protected boolean isConcrete(Class<?> clazz) {
148         return !Modifier.isAbstract(clazz.getModifiers());
149     }
150 
151     // TODO - is this needed / correct ?
152     public <C> Class<? extends C> getImplementation(Class<C> type) throws ClassNotFoundException {
153         final String className = getImplementationName(type);
154         if (!isInRepositoryDefinition(className)) {
155             return (Class<? extends C>) Classes.getClassFactory().forName(className);
156         } else {
157             return type;
158         }
159     }
160 
161     protected String getImplementationName(Class<?> type) {
162         final String name = type.getName();
163         return mappings.getProperty(name, name);
164     }
165 
166     private boolean isInRepositoryDefinition(String className) {
167         return className.startsWith("/") || className.indexOf(':') >= 0;
168     }
169 
170     /**
171      * Used only in tests.
172      * @see {@link info.magnolia.test.ComponentsTestUtil}
173      */
174     public void setImplementation(Class<?> type, String impl) {
175         mappings.setProperty(type.getName(), impl);
176     }
177 
178     /**
179      * Used only in tests.
180      * @see {@link info.magnolia.test.ComponentsTestUtil}
181      */
182     public void setInstance(Class<?> type, Object instance) {
183         instances.put(type, instance);
184     }
185 
186     /**
187      * Used only in tests.
188      * @see {@link info.magnolia.test.ComponentsTestUtil}
189      */
190     public void setInstanceFactory(Class<?> type, ComponentFactory<?> factory) {
191         factories.put(type, factory);
192     }
193 
194     /**
195      * Used only in tests.
196      * <strong>Warning:</strong> this does NOT clear the *mappings*. With the current/default implementation,
197      * this means tests also have to call SystemProperty.clearr()
198      * @see {@link info.magnolia.test.ComponentsTestUtil}
199      */
200     public void clear() {
201         factories.clear();
202         instances.clear();
203     }
204 
205 }