View Javadoc

1   /**
2    * This file Copyright (c) 2010-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.module.blossom.context;
35  
36  import javax.jcr.Node;
37  import javax.jcr.RepositoryException;
38  import javax.jcr.observation.EventIterator;
39  import javax.jcr.observation.EventListener;
40  
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  import org.springframework.aop.TargetSource;
44  import org.springframework.aop.framework.ProxyFactoryBean;
45  import org.springframework.beans.factory.BeanFactory;
46  import org.springframework.beans.factory.BeanFactoryAware;
47  import org.springframework.beans.factory.BeanNameAware;
48  import org.springframework.beans.factory.DisposableBean;
49  import org.springframework.beans.factory.InitializingBean;
50  import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
51  import org.springframework.context.ApplicationContext;
52  import org.springframework.context.ApplicationContextAware;
53  import org.springframework.context.ApplicationEventPublisher;
54  import org.springframework.context.ApplicationEventPublisherAware;
55  
56  import info.magnolia.cms.util.ContentUtil;
57  import info.magnolia.cms.util.ObservationUtil;
58  import info.magnolia.content2bean.Content2BeanException;
59  import info.magnolia.content2bean.Content2BeanUtil;
60  import info.magnolia.context.SystemContext;
61  import info.magnolia.module.blossom.content2bean.SpringContent2BeanTransformer;
62  import info.magnolia.module.blossom.support.BeanFactoryUtils;
63  import info.magnolia.objectfactory.Components;
64  import info.magnolia.repository.RepositoryConstants;
65  
66  /**
67   * FactoryBean that creates a bean and populates its properties with values from the repository and reloads the bean
68   * automatically when the repository is updated.
69   *
70   * The returned instance is a proxy that delegates calls to the instance created from the repository. The instance
71   * delegated to is transparently swapped. The proxy is by default a JDK dynamic proxy proxying all the objects
72   * interfaces. This can be changed to a cglib proxy by setting the property proxyTargetClass to true.
73   *
74   * There is a delay mechanism in place that aggregates events in order to postpone reloading while the repository is
75   * being updated. By default any change postpones reloading by 15 seconds but no longer than 60 seconds. These values
76   * can be configured.
77   *
78   * @since 1.2
79   */
80  public class ObservedBeanFactoryBean extends ProxyFactoryBean implements InitializingBean, BeanNameAware, ApplicationContextAware, BeanFactoryAware, EventListener, DisposableBean, ApplicationEventPublisherAware {
81  
82      private static final Logger logger = LoggerFactory.getLogger(ObservedBeanFactoryBean.class);
83  
84      private static final String DEFAULT_WORKSPACE = RepositoryConstants.CONFIG;
85      private static final int DEFAULT_MAX_DELAY = 60000;
86      private static final int DEFAULT_DELAY = 15000;
87  
88      private ApplicationContext applicationContext;
89      private ApplicationEventPublisher applicationEventPublisher;
90      private AbstractAutowireCapableBeanFactory beanFactory;
91      private String beanName;
92      private String workspace = DEFAULT_WORKSPACE;
93      private String path;
94      private int maxObservationDelay = DEFAULT_MAX_DELAY;
95      private int observationDelay = DEFAULT_DELAY;
96      private Class<?> defaultClass;
97  
98      private volatile Object target;
99  
100     public ObservedBeanFactoryBean() {
101         super.setSingleton(true);
102 
103         super.setTargetSource(new TargetSource() {
104 
105             @Override
106             public Class<?> getTargetClass() {
107                 Object local = target;
108                 return local != null ? local.getClass() : null;
109             }
110 
111             @Override
112             public boolean isStatic() {
113                 return false;
114             }
115 
116             @Override
117             public Object getTarget() throws Exception {
118                 return target;
119             }
120 
121             @Override
122             public void releaseTarget(Object target) throws Exception {
123             }
124         });
125     }
126 
127     public Class<?> getDefaultClass() {
128         return defaultClass;
129     }
130 
131     public void setDefaultClass(Class<?> defaultClass) {
132         this.defaultClass = defaultClass;
133     }
134 
135     @Deprecated
136     public String getRepository() {
137         return workspace;
138     }
139 
140     @Deprecated
141     public void setRepository(String repository) {
142         this.workspace = repository;
143     }
144 
145     public String getWorkspace() {
146         return workspace;
147     }
148 
149     public void setWorkspace(String workspace) {
150         this.workspace = workspace;
151     }
152 
153     public String getPath() {
154         return path;
155     }
156 
157     public void setPath(String path) {
158         this.path = path;
159     }
160 
161     public void setMaxObservationDelay(int maxObservationDelay) {
162         this.maxObservationDelay = maxObservationDelay;
163     }
164 
165     public void setObservationDelay(int observationDelay) {
166         this.observationDelay = observationDelay;
167     }
168 
169     @Override
170     public void afterPropertiesSet() throws Exception {
171         this.target = createInstance();
172         startObservation();
173     }
174 
175     @Override
176     public void destroy() throws Exception {
177         stopObservation();
178         Object instance = target;
179         target = null;
180         destroyInstance(instance);
181     }
182 
183     @Override
184     public void onEvent(EventIterator events) {
185         try {
186             reloadInstance();
187         } catch (Exception e) {
188             logger.error("Failed to reload observed bean [" + beanName + "] configured at path [" + path + "] in workspace [" + workspace + "] on change event", e);
189         }
190     }
191 
192     @Override
193     public void setApplicationContext(ApplicationContext applicationContext) {
194         this.applicationContext = applicationContext;
195     }
196 
197     @Override
198     public void setBeanFactory(BeanFactory beanFactory) {
199         this.beanFactory = (AbstractAutowireCapableBeanFactory) beanFactory;
200     }
201 
202     @Override
203     public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
204         this.applicationEventPublisher = applicationEventPublisher;
205     }
206 
207     @Override
208     public void setBeanName(String beanName) {
209         this.beanName = beanName;
210     }
211 
212     protected Object createInstance() throws Content2BeanException, RepositoryException {
213         Node node = getConfigurationNode();
214         return transformNode(node);
215     }
216 
217     protected void destroyInstance(Object instance) {
218         if (instance != null) {
219             BeanFactoryUtils.destroyBean(instance, beanName, beanFactory);
220         }
221     }
222 
223     protected synchronized void reloadInstance() throws Content2BeanException, RepositoryException {
224         Object previousInstance = this.target;
225         this.target = createInstance();
226         destroyInstance(previousInstance);
227 
228         applicationEventPublisher.publishEvent(new ObservedBeanReloadedEvent(applicationContext, target, beanName, workspace, path));
229     }
230 
231     protected Object transformNode(Node node) throws Content2BeanException {
232         SpringContent2BeanTransformer transformer = new SpringContent2BeanTransformer(beanFactory);
233         transformer.setTopLevelBeanName(beanName);
234         transformer.setDefaultClass(defaultClass);
235         return Content2BeanUtil.toBean(ContentUtil.asContent(node), true, transformer);
236     }
237 
238     protected Node getConfigurationNode() throws RepositoryException {
239         return Components.getComponent(SystemContext.class).getJCRSession(workspace).getNode(path);
240     }
241 
242     protected void startObservation() {
243         ObservationUtil.registerDeferredChangeListener(workspace, path, this, observationDelay, maxObservationDelay);
244     }
245 
246     protected void stopObservation() {
247         ObservationUtil.unregisterChangeListener(workspace, this);
248     }
249 }