1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.config.source.ConfigurationSourceTypes;
43 import info.magnolia.context.MgnlContext;
44 import info.magnolia.jcr.node2bean.Node2BeanException;
45 import info.magnolia.jcr.node2bean.Node2BeanProcessor;
46 import info.magnolia.jcr.util.NodeTypes;
47 import info.magnolia.jcr.util.NodeUtil;
48 import info.magnolia.jcr.util.PropertyUtil;
49 import info.magnolia.jcr.util.SessionUtil;
50 import info.magnolia.jcr.wrapper.ExtendingNodeWrapper;
51 import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper;
52 import info.magnolia.module.ModuleLifecycle;
53 import info.magnolia.module.ModuleLifecycleContext;
54 import info.magnolia.module.site.theme.Theme;
55 import info.magnolia.module.site.theme.registry.ThemeRegistry;
56 import info.magnolia.objectfactory.Components;
57 import info.magnolia.observation.WorkspaceEventListenerRegistration;
58 import info.magnolia.repository.RepositoryConstants;
59
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65
66 import javax.inject.Inject;
67 import javax.inject.Singleton;
68 import javax.jcr.Node;
69 import javax.jcr.Property;
70 import javax.jcr.PropertyIterator;
71 import javax.jcr.RepositoryException;
72 import javax.jcr.Session;
73 import javax.jcr.observation.EventIterator;
74 import javax.jcr.observation.EventListener;
75
76 import org.apache.commons.lang3.StringUtils;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80
81
82
83 @Singleton
84 public class SiteModule implements ModuleLifecycle {
85
86 private static final Logger log = LoggerFactory.getLogger(SiteModule.class);
87
88 public static final String SITE_MODULE_PATH = "/modules/site/config/site";
89
90 private static final long DELAY = 5000;
91
92 private static final long MAX_DELAY = 30000;
93
94 private static final String EXTENDS_PROPERTY = "extends";
95
96 private static final String EXTENDS_OVERRIDE = "override";
97
98 private final EventListener siteModuleEventListener = new SiteModuleEventListener();
99
100 private final Node2BeanProcessor node2BeanProcessor;
101
102 private Site site;
103
104 private final ConfigurationSourceFactory configSourceFactory;
105
106 private final ThemeRegistry themeRegistry;
107
108 private final Collection<DefinitionMetadata> oldLocationThemes = new ArrayList<>();
109
110
111
112
113 protected static final Site NULL_SITE = new NullSite();
114
115 private WorkspaceEventListenerRegistration.Handle eventListenerRegistration;
116
117 @Inject
118 public SiteModule(Node2BeanProcessor node2BeanProcessor, ConfigurationSourceFactory configSourceFactory, ThemeRegistry themeRegistry) {
119 this.node2BeanProcessor = node2BeanProcessor;
120 this.configSourceFactory = configSourceFactory;
121 this.themeRegistry = themeRegistry;
122 }
123
124
125
126
127 @Deprecated
128 public SiteModule(Node2BeanProcessor node2BeanProcessor) {
129 this(Components.getComponent(Node2BeanProcessor.class), Components.getComponent(ConfigurationSourceFactory.class), Components.getComponent(ThemeRegistry.class));
130 }
131
132
133
134
135 @Deprecated
136 public SiteModule() {
137 this(Components.getComponent(Node2BeanProcessor.class), Components.getComponent(ConfigurationSourceFactory.class), Components.getComponent(ThemeRegistry.class));
138 }
139
140
141
142
143
144 public Site getSite() {
145 if (this.site == null) {
146 return NULL_SITE;
147 }
148 return this.site;
149 }
150
151 public void setSite(Site site) {
152 this.site = site;
153 }
154
155
156
157
158 @Deprecated
159 public Map<String, Theme> getThemes() {
160 Map<String, Theme> result = new HashMap<>();
161 Collection<Theme> themes = themeRegistry.getAllDefinitions();
162 for (Theme theme : themes) {
163 result.put(theme.getName(), theme);
164 }
165 return result;
166 }
167
168
169
170
171 @Deprecated
172 public void setThemes(Map<String, Theme> themes) {
173 themeRegistry.unregisterAndRegister(oldLocationThemes, new ArrayList<DefinitionProvider<Theme>>());
174 oldLocationThemes.clear();
175 for (String key : themes.keySet()) {
176 this.addTheme(key, themes.get(key));
177 }
178 }
179
180
181
182
183 @Deprecated
184 public void addTheme(String name, Theme theme) {
185 log.warn("Deprecated location ( [/modules/site/config/themes/{}]) for theme configuration used. Please move your theme under [/modules/<module-name>/themes].", name);
186 final String moduleName = "site";
187 final String relativeLocation = "/config/themes/" + name;
188 final String location = "/modules/" + moduleName + relativeLocation;
189 final DefinitionProvider<Theme> definitionProvider = DefinitionProviderBuilder.<Theme>newBuilder()
190 .metadata(themeRegistry
191 .newMetadataBuilder()
192 .type(themeRegistry.type())
193 .configurationSourceType(ConfigurationSourceTypes.jcr)
194 .module(moduleName)
195 .location(location)
196 .relativeLocation(relativeLocation)
197 .name(name))
198 .rawView(getRawViewFromNode(SessionUtil.getNode(RepositoryConstants.CONFIG, location)))
199 .definition(theme)
200 .build();
201 themeRegistry.register(definitionProvider);
202 oldLocationThemes.add(definitionProvider.getMetadata());
203 }
204
205
206
207
208 @Deprecated
209 public Theme getTheme(String name) {
210 return themeRegistry.getProvider(name).get();
211 }
212
213 @Override
214 public void start(ModuleLifecycleContext moduleLifecycleContext) {
215 if (site == null) {
216 log.warn("Currently there is no default site specified in the site module. Make sure to add a site configuration to [{}].", SITE_MODULE_PATH);
217 }
218 String extendedNodePath = getExtendedNodePath();
219 if (StringUtils.isNotEmpty(extendedNodePath)) {
220 try {
221 eventListenerRegistration = WorkspaceEventListenerRegistration.observe(RepositoryConstants.CONFIG, extendedNodePath, siteModuleEventListener).withDelay(DELAY, MAX_DELAY).register();
222 } catch (RepositoryException e) {
223 log.warn("Unable to register event listener for [{}:{}]", RepositoryConstants.CONFIG, extendedNodePath, e);
224 }
225 }
226
227 if (moduleLifecycleContext.getPhase() == ModuleLifecycleContext.PHASE_SYSTEM_STARTUP) {
228 configSourceFactory.jcr().bindWithDefaults(themeRegistry);
229 configSourceFactory.yaml().bindWithDefaults(themeRegistry);
230 }
231 }
232
233 @Override
234 public void stop(ModuleLifecycleContext moduleLifecycleContext) {
235 if (eventListenerRegistration != null) {
236 try {
237 eventListenerRegistration.unregister();
238 } catch (RepositoryException e) {
239 log.warn("Unable to unregister event listener for [{}:{}]", RepositoryConstants.CONFIG, getExtendedNodePath(), e);
240 }
241 }
242 }
243
244
245
246
247
248
249 private String getExtendedNodePath() {
250 try {
251 final Session session = MgnlContext.getJCRSession(RepositoryConstants.CONFIG);
252 final Node siteNode = session.getNode(SITE_MODULE_PATH);
253 final String extendingNodePath = PropertyUtil.getString(siteNode, EXTENDS_PROPERTY, null);
254 if (StringUtils.isNotBlank(extendingNodePath) && !EXTENDS_OVERRIDE.equals(extendingNodePath)) {
255 final Node extendedNode;
256 if (extendingNodePath.startsWith("/")) {
257 extendedNode = session.getNode(extendingNodePath);
258 } else {
259 extendedNode = siteNode.getNode(extendingNodePath);
260 }
261 return extendedNode.getPath();
262 }
263 } catch (RepositoryException e) {
264 log.debug("Unable to obtain extended node path", e);
265 }
266 return null;
267 }
268
269
270
271
272
273
274 private Node getSiteNode() {
275
276 try {
277 return MgnlContext.doInSystemContext(new JCRSessionOp<Node>(RepositoryConstants.CONFIG) {
278 @Override
279 public Node exec(final Session session) throws RepositoryException {
280 return session.getNode(SITE_MODULE_PATH);
281 }
282 });
283 } catch (RepositoryException e) {
284 log.debug("Unable to obtain site node");
285 }
286 return null;
287 }
288
289
290
291
292 private class SiteModuleEventListener implements EventListener {
293 @Override
294 public void onEvent(final EventIterator events) {
295 Node siteNode = getSiteNode();
296 if (siteNode != null) {
297 try {
298 site = (Site) node2BeanProcessor.toBean(siteNode, Site.class);
299 } catch (Node2BeanException | RepositoryException e) {
300 log.debug("Error occurred while transforming site node to bean class", e);
301 }
302 }
303 }
304 }
305
306 private DefinitionRawView getRawViewFromNode(Node node) {
307 if (node == null) {
308 return DefinitionRawView.EMPTY;
309 }
310
311 final List<DefinitionRawView.Property> properties = resolveRawViewProperties(new ExtendingNodeWrapper(new JCRMgnlPropertiesFilteringNodeWrapper(node)));
312 return () -> properties;
313 }
314
315 private List<DefinitionRawView.Property> resolveRawViewProperties(Node node) {
316 final List<DefinitionRawView.Property> properties = new ArrayList<>();
317
318 try {
319 for (final PropertyIterator pit = node.getProperties(); pit.hasNext(); ) {
320 final Property property = pit.nextProperty();
321 properties.add(DefinitionRawView.Property.simple(property.getName(), property.getString()));
322 }
323
324 for (final Node childNode : NodeUtil.getNodes(node)) {
325 final List<DefinitionRawView.Property> childProperties = resolveRawViewProperties(childNode);
326 if (childNode.getPrimaryNodeType().getName().equals(NodeTypes.ContentNode.NAME)) {
327 properties.add(DefinitionRawView.Property.subBean(childNode.getName(), () -> childProperties));
328 } else {
329 properties.add(DefinitionRawView.Property.collection(childNode.getName(), childProperties));
330 }
331 }
332 } catch (RepositoryException e) {
333 log.error("Failed to resolve definition raw view properties of [{}] due to: {}", NodeUtil.getPathIfPossible(node), e.getMessage(), e);
334 }
335
336 return properties;
337 }
338
339 }