View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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.core.Content;
37  import info.magnolia.cms.security.SilentSessionOp;
38  import info.magnolia.cms.util.ContentUtil;
39  import info.magnolia.cms.util.ObservationUtil;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.jcr.node2bean.Node2BeanException;
42  import info.magnolia.jcr.node2bean.Node2BeanProcessor;
43  import info.magnolia.jcr.node2bean.Node2BeanTransformer;
44  import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl;
45  
46  import javax.jcr.Node;
47  import javax.jcr.RepositoryException;
48  import javax.jcr.Session;
49  import javax.jcr.observation.EventIterator;
50  import javax.jcr.observation.EventListener;
51  
52  import org.apache.commons.proxy.ObjectProvider;
53  import org.apache.commons.proxy.factory.cglib.CglibProxyFactory;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  /**
58   * Generic observed singleton factory.
59   * @param <T> the type of component this factory instantiates.
60   *
61   * @author philipp
62   * @version $Id: $
63   */
64  public class ObservedComponentFactory<T> implements ComponentFactory<T>, EventListener {
65      private static final Logger log = LoggerFactory.getLogger(ObservedComponentFactory.class);
66  
67      private static final int DEFAULT_MAX_DELAY = 5000;
68      private static final int DEFAULT_DELAY = 1000;
69  
70      /**
71       * Repository name used.
72       */
73      private final String repository;
74  
75      /**
76       * Path to the node in the config repository.
77       */
78      private final String path;
79  
80      /**
81       * @deprecated since 4.3 - this should be private - use {@link #getComponentType()} instead.
82       * (rename to "type" once made private)
83       */
84      @Deprecated
85      protected final Class<T> interf;
86  
87      /**
88       * The object delivered by this factory.
89       * @deprecated since 4.3 - this should be private - use {@link #getObservedObject()} instead.
90       */
91      @Deprecated
92      protected T observedObject;
93  
94      private ComponentProvider componentProvider;
95  
96      public ObservedComponentFactory(String repository, String path, Class<T> type) {
97          this(repository, path, type, Components.getComponentProvider());
98      }
99  
100     public ObservedComponentFactory(String repository, String path, Class<T> type, ComponentProvider componentProvider) {
101         this.repository = repository;
102         this.path = path;
103         this.interf = type;
104         this.componentProvider = componentProvider;
105         load();
106         startObservation(path);
107     }
108 
109     @Override
110     @SuppressWarnings("unchecked")
111     // until commons-proxy becomes generics-aware, we have to ignore this warning
112     public T newInstance() {
113         if (getObservedObject() == null) {
114             // TODO - replace this by a default implementation or some form of null proxy
115             // this only happens if load() did not set observedObject
116             log.warn("An instance of {} couldn't be loaded from {}:{} yet, returning null.", new Object[]{interf, repository, path});
117             return null;
118         }
119 
120         return (T) new CglibProxyFactory().createDelegatorProxy(new ObjectProvider() {
121             @Override
122             public Object getObject() {
123                 return getObservedObject();
124             }
125         }, new Class[]{
126                 // we want to expose the observed object's concrete class and interfaces so that client code can cast if they want
127                 getObservedObject().getClass()
128         });
129     }
130 
131     protected void startObservation(String handle) {
132         ObservationUtil.registerDeferredChangeListener(repository, handle, this, DEFAULT_DELAY, DEFAULT_MAX_DELAY);
133     }
134 
135     @Override
136     public void onEvent(EventIterator events) {
137         reload();
138     }
139 
140     protected void reload() {
141         load();
142     }
143 
144     protected void load() {
145         MgnlContext.doInSystemContext(new SilentSessionOp<Void>(repository) {
146 
147             @Override
148             public Void doExec(Session session) throws RepositoryException {
149                 session = MgnlContext.getJCRSession(session.getWorkspace().getName());
150                 if (session.nodeExists(path)) {
151                     try {
152                         // TODO: change this once c2b is n2b ...
153                         final Node node = session.getNode(path);
154                         onRegister(ContentUtil.asContent(node));
155                     } catch (RepositoryException e) {
156                         log.error("Can't read configuration for " + interf + " from [" + repository + ":" + path + "], will return null.", e);
157                     }
158                 } else {
159                     log.debug("{} does not exist, will return a default implementation for {}.", path, interf);
160                     instantiateDefault();
161                 }
162                 return null;
163             }
164 
165             @Override
166             public String toString() {
167                 return " load repository [" + repository + "] path [" + path + "].";
168             }
169         });
170     }
171 
172     protected void instantiateDefault() {
173         if (Classes.isConcrete(interf)) {
174             log.info("{} does not exist, will return a new instance of {}.", path, interf);
175             final ClassFactory classFactory = Classes.getClassFactory();
176             this.observedObject = classFactory.newInstance(interf);
177         } else {
178             log.warn("{} does not exist, default implementation for {} is unknown, will return null.", path, interf);
179         }
180     }
181 
182     /**
183      * @deprecated since 4.5, use {@link Classes#isConcrete(Class)}
184      */
185     @Deprecated
186     protected boolean isConcrete(Class<?> clazz) {
187         return Classes.isConcrete(clazz);
188     }
189 
190     /**
191      * @deprecated since 4.5, use {@link #onRegister(Node)} instead
192      */
193     @Deprecated
194     protected void onRegister(Content node) {
195         try {
196             Node n = node.getJCRNode();
197             final T instance = transformNode(n);
198 
199             if (this.observedObject != null) {
200                 log.info("Re-loaded {} from {}", interf.getName(), node.getHandle());
201             } else {
202                 log.debug("Loading {} from {}", interf.getName(), node.getHandle());
203             }
204             this.observedObject = instance;
205 
206         } catch (Exception e) {
207             log.error("Can't transform [" + repository + ":" + path + "] to " + interf, e);
208         }
209     }
210 
211     protected T transformNode(Node node) throws Node2BeanException, RepositoryException {
212         return (T) Components.getComponent(Node2BeanProcessor.class).toBean(node, true, getNode2BeanTransformer(), componentProvider);
213     }
214 
215     protected Node2BeanTransformer getNode2BeanTransformer() {
216         return new Node2BeanTransformerImpl();
217     }
218 
219     protected Class<T> getComponentType() {
220         return interf;
221     }
222 
223     /**
224      * Returns the latest converted object observed by this factory.
225      * Since 4.3, if you are using {@link info.magnolia.objectfactory.DefaultClassFactory}, calling this shouldn't be needed,
226      * {@link #newInstance()} returned a proxy, so you'll always see this object.
227      *
228      * @deprecated since 4.3 - {@link info.magnolia.objectfactory.DefaultComponentProvider#newInstance(Class)} returns a proxy of the observed object instead of this factory, so this method shouldn't be needed publicly.
229      */
230     @Deprecated
231     public T getObservedObject() {
232         return this.observedObject;
233     }
234 
235     @Override
236     public String toString() {
237         return super.toString() + ":" + interf + "(Observing: " + repository + ":" + path + ")";
238     }
239 }