View Javadoc
1   /**
2    * This file Copyright (c) 2014-2016 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.site;
35  
36  import info.magnolia.cms.security.JCRSessionOp;
37  import info.magnolia.config.registry.DefinitionMetadata;
38  import info.magnolia.config.registry.DefinitionProvider;
39  import info.magnolia.config.registry.DefinitionProviderBuilder;
40  import info.magnolia.config.registry.DefinitionRawView;
41  import info.magnolia.config.source.ConfigurationSourceFactory;
42  import info.magnolia.context.MgnlContext;
43  import info.magnolia.jcr.node2bean.Node2BeanException;
44  import info.magnolia.jcr.node2bean.Node2BeanProcessor;
45  import info.magnolia.jcr.util.PropertyUtil;
46  import info.magnolia.module.ModuleLifecycle;
47  import info.magnolia.module.ModuleLifecycleContext;
48  import info.magnolia.module.site.theme.Theme;
49  import info.magnolia.module.site.theme.registry.ThemeRegistry;
50  import info.magnolia.objectfactory.Components;
51  import info.magnolia.observation.WorkspaceEventListenerRegistration;
52  import info.magnolia.repository.RepositoryConstants;
53  
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.Map;
58  
59  import javax.inject.Inject;
60  import javax.inject.Singleton;
61  import javax.jcr.Node;
62  import javax.jcr.RepositoryException;
63  import javax.jcr.Session;
64  import javax.jcr.observation.EventIterator;
65  import javax.jcr.observation.EventListener;
66  
67  import org.apache.commons.lang3.StringUtils;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * Module class.
73   */
74  @Singleton
75  public class SiteModule implements ModuleLifecycle {
76  
77      private static final Logger log = LoggerFactory.getLogger(SiteModule.class);
78  
79      public static final String SITE_MODULE_PATH = "/modules/site/config/site";
80  
81      private static final long DELAY = 5000;
82  
83      private static final long MAX_DELAY = 30000;
84  
85      private static final String EXTENDS_PROPERTY = "extends";
86  
87      private static final String EXTENDS_OVERRIDE = "override";
88  
89      private final EventListener siteModuleEventListener = new SiteModuleEventListener();
90  
91      private final Node2BeanProcessor node2BeanProcessor;
92  
93      private Site site;
94  
95      private final ConfigurationSourceFactory configSourceFactory;
96  
97      private final ThemeRegistry themeRegistry;
98  
99      private final Collection<DefinitionMetadata> oldLocationThemes = new ArrayList<>();
100 
101     /**
102      * {@link NullSite} object returned when there is no {@link Site} configured.
103      */
104     protected static final Site NULL_SITE = new NullSite();
105 
106     private WorkspaceEventListenerRegistration.Handle eventListenerRegistration;
107 
108     @Inject
109     public SiteModule(Node2BeanProcessor node2BeanProcessor, ConfigurationSourceFactory configSourceFactory, ThemeRegistry themeRegistry) {
110         this.node2BeanProcessor = node2BeanProcessor;
111         this.configSourceFactory = configSourceFactory;
112         this.themeRegistry = themeRegistry;
113     }
114 
115     /**
116      * @deprecated since 1.0.6 - use {@link #SiteModule(Node2BeanProcessor, ConfigurationSourceFactory, ThemeRegistry)} instead.
117      */
118     @Deprecated
119     public SiteModule(Node2BeanProcessor node2BeanProcessor) {
120         this(Components.getComponent(Node2BeanProcessor.class), Components.getComponent(ConfigurationSourceFactory.class), Components.getComponent(ThemeRegistry.class));
121     }
122 
123     /**
124      * @deprecated since 1.0.1 - use {@link #SiteModule(Node2BeanProcessor, ConfigurationSourceFactory, ThemeRegistry)} instead.
125      */
126     @Deprecated
127     public SiteModule() {
128         this(Components.getComponent(Node2BeanProcessor.class), Components.getComponent(ConfigurationSourceFactory.class), Components.getComponent(ThemeRegistry.class));
129     }
130 
131     /**
132      * Returns the {@link Site} configured under <code>/modules/site/config/site</code> (see {@link #SITE_MODULE_PATH})
133      * or an instance of {@link NullSite} when none is configured.
134      */
135     public Site getSite() {
136         if (this.site == null) {
137             return NULL_SITE;
138         }
139         return this.site;
140     }
141 
142     public void setSite(Site site) {
143         this.site = site;
144     }
145 
146     /**
147      * @deprecated since 1.0.6 - use {@link ThemeRegistry} IoC instead.
148      */
149     @Deprecated
150     public Map<String, Theme> getThemes() {
151         Map<String, Theme> result = new HashMap<>();
152         Collection<Theme> themes = themeRegistry.getAllDefinitions();
153         for (Theme theme : themes) {
154             result.put(theme.getName(), theme);
155         }
156         return result;
157     }
158 
159     /**
160      * @deprecated since 1.0.6 - use {@link ThemeRegistry} IoC instead.
161      */
162     @Deprecated
163     public void setThemes(Map<String, Theme> themes) {
164         themeRegistry.unregisterAndRegister(oldLocationThemes, new ArrayList<DefinitionProvider<Theme>>());
165         oldLocationThemes.clear();
166         for (String key : themes.keySet()) {
167             this.addTheme(key, themes.get(key));
168         }
169     }
170 
171     /**
172      * @deprecated since 1.0.6 - use {@link ThemeRegistry} IoC instead.
173      */
174     @Deprecated
175     public void addTheme(String name, Theme theme) {
176         log.warn("Deprecated location ( [/modules/site/config/themes/{}]) for theme configuration used. Please move your theme under [/modules/<module-name>/themes].", name);
177         final String moduleName = "site";
178         final String relativeLocation = "/config/themes/" + name;
179         final String location = "/modules/" + moduleName + relativeLocation;
180         final DefinitionProvider<Theme> definitionProvider = DefinitionProviderBuilder.<Theme>newBuilder()
181                 .metadata(themeRegistry.newMetadataBuilder().type(themeRegistry.type()).module(moduleName).location(location).relativeLocation(relativeLocation).name(name))
182                 .rawView(DefinitionRawView.EMPTY) // We have no raw view for this, but the whole provider should still be considered valid.
183                 .definition(theme)
184                 .build();
185         themeRegistry.register(definitionProvider);
186         oldLocationThemes.add(definitionProvider.getMetadata());
187     }
188 
189     /**
190      * @deprecated since 1.0.6 - use {@link ThemeRegistry} IoC instead.
191      */
192     @Deprecated
193     public Theme getTheme(String name) {
194         return themeRegistry.getProvider(name).get();
195     }
196 
197     @Override
198     public void start(ModuleLifecycleContext moduleLifecycleContext) {
199         if (site == null) {
200             log.warn("Currently there is no default site specified in the site module. Make sure to add a site configuration to [{}].", SITE_MODULE_PATH);
201         }
202         String extendedNodePath = getExtendedNodePath();
203         if (StringUtils.isNotEmpty(extendedNodePath)) {
204             try {
205                 eventListenerRegistration = WorkspaceEventListenerRegistration.observe(RepositoryConstants.CONFIG, extendedNodePath, siteModuleEventListener).withDelay(DELAY, MAX_DELAY).register();
206             } catch (RepositoryException e) {
207                 log.warn("Unable to register event listener for [{}:{}]", RepositoryConstants.CONFIG, extendedNodePath, e);
208             }
209         }
210 
211         if (moduleLifecycleContext.getPhase() == ModuleLifecycleContext.PHASE_SYSTEM_STARTUP) {
212             configSourceFactory.jcr().bindWithDefaults(themeRegistry);
213             configSourceFactory.yaml().bindWithDefaults(themeRegistry);
214         }
215     }
216 
217     @Override
218     public void stop(ModuleLifecycleContext moduleLifecycleContext) {
219         if (eventListenerRegistration != null) {
220             try {
221                 eventListenerRegistration.unregister();
222             } catch (RepositoryException e) {
223                 log.warn("Unable to unregister event listener for [{}:{}]", RepositoryConstants.CONFIG, getExtendedNodePath(), e);
224             }
225         }
226     }
227 
228     /**
229      * Obtains extended node for {@link #SITE_MODULE_PATH}.
230      *
231      * @return extended node or null
232      */
233     private String getExtendedNodePath() {
234         try {
235             final Session session = MgnlContext.getJCRSession(RepositoryConstants.CONFIG);
236             final Node siteNode = session.getNode(SITE_MODULE_PATH);
237             final String extendingNodePath = PropertyUtil.getString(siteNode, EXTENDS_PROPERTY, null);
238             if (StringUtils.isNotBlank(extendingNodePath) && !EXTENDS_OVERRIDE.equals(extendingNodePath)) {
239                 final Node extendedNode;
240                 if (extendingNodePath.startsWith("/")) {
241                     extendedNode = session.getNode(extendingNodePath);
242                 } else {
243                     extendedNode = siteNode.getNode(extendingNodePath);
244                 }
245                 return extendedNode.getPath();
246             }
247         } catch (RepositoryException e) {
248             log.debug("Unable to obtain extended node path", e);
249         }
250         return null;
251     }
252 
253     /**
254      * Returns site node for path {@link #SITE_MODULE_PATH}.
255      *
256      * @return site node or null
257      */
258     private Node getSiteNode() {
259         // operation is done in system context because context is not set when event is fired by observation mechanism.
260         try {
261             return MgnlContext.doInSystemContext(new JCRSessionOp<Node>(RepositoryConstants.CONFIG) {
262                 @Override
263                 public Node exec(final Session session) throws RepositoryException {
264                     return session.getNode(SITE_MODULE_PATH);
265                 }
266             });
267         } catch (RepositoryException e) {
268             log.debug("Unable to obtain site node");
269         }
270         return null;
271     }
272 
273     /**
274      * Simple listener that just reloads site field.
275      */
276     private class SiteModuleEventListener implements EventListener {
277         @Override
278         public void onEvent(final EventIterator events) {
279             Node siteNode = getSiteNode();
280             if (siteNode != null) {
281                 try {
282                     site = (Site) node2BeanProcessor.toBean(siteNode, Site.class);
283                 } catch (Node2BeanException | RepositoryException e) {
284                     log.debug("Error occurred while transforming site node to bean class", e);
285                 }
286             }
287         }
288     }
289 
290 }