View Javadoc
1   /**
2    * This file Copyright (c) 2015-2018 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.resourceloader.jcr;
35  
36  import static info.magnolia.repository.RepositoryConstants.WEBSITE;
37  import static info.magnolia.resourceloader.ResourceOriginChange.Type.*;
38  import static info.magnolia.resourceloader.ResourceOriginChangeMatcher.resourceChange;
39  import static info.magnolia.resourceloader.jcr.JcrResourceOrigin.*;
40  import static org.junit.Assert.*;
41  import static org.mockito.Matchers.argThat;
42  import static org.mockito.Mockito.*;
43  
44  import info.magnolia.context.MgnlContext;
45  import info.magnolia.context.SystemContext;
46  import info.magnolia.jcr.util.NodeTypes;
47  import info.magnolia.jcr.util.NodeUtil;
48  import info.magnolia.jcr.wrapper.DelegateSessionWrapper;
49  import info.magnolia.resourceloader.ResourceChangeHandler;
50  import info.magnolia.resourceloader.ResourceOriginChange;
51  import info.magnolia.test.RepositoryTestCase;
52  import info.magnolia.test.TestUtil;
53  
54  import javax.jcr.Node;
55  import javax.jcr.Session;
56  import javax.jcr.nodetype.NodeType;
57  import javax.jcr.observation.EventListenerIterator;
58  import javax.jcr.observation.ObservationManager;
59  import javax.jcr.version.VersionManager;
60  
61  import org.junit.After;
62  import org.junit.Before;
63  import org.junit.BeforeClass;
64  import org.junit.Test;
65  
66  /**
67   * Repository test for {@link JcrResourceOrigin}, to cover peculiarities of jackrabbit observation.
68   */
69  public class JcrResourceOriginRepositoryTest extends RepositoryTestCase {
70  
71      private static final int TIMEOUT = 2000;
72  
73      private Session session;
74      private JcrResourceOrigin jcrOrigin;
75  
76      @BeforeClass
77      public static void shuntSlf4jSimpleLoggerForJackrabbit() {
78          TestUtil.shuntLogFor("org.apache.jackrabbit");
79          TestUtil.shuntLogFor(info.magnolia.jackrabbit.ProviderImpl.class);
80      }
81  
82      @Override
83      @Before
84      public void setUp() throws Exception {
85          super.setUp();
86          final SystemContext ctx = (SystemContext) MgnlContext.getInstance();
87  
88          this.session = ctx.getJCRSession(RESOURCES_WORKSPACE);
89          this.jcrOrigin = new JcrResourceOrigin(() -> ctx, "jcr");
90      }
91  
92      @Override
93      @After
94      public void tearDown() throws Exception {
95          ObservationManager observationManager = session.getWorkspace().getObservationManager();
96          EventListenerIterator eventListeners = observationManager.getRegisteredEventListeners();
97          while (eventListeners.hasNext()) {
98              observationManager.removeEventListener(eventListeners.nextEventListener());
99          }
100         super.tearDown();
101     }
102 
103     @Override
104     protected String getRepositoryConfigFileName() {
105         return "test-resource-repositories.xml";
106     }
107 
108     @Test
109     public void communicatesFileResourceAddition() throws Exception {
110         // GIVEN
111         // register a resource change handler
112         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
113         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
114 
115         // WHEN we create a file node
116         NodeUtil.createPath(session.getRootNode(), "foo/dialogs/bar.yaml", NodeTypes.ContentNode.NAME, true);
117 
118         // THEN handler is notified with a correct change type, path and related origin
119         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(ADDED).at("/foo/dialogs/bar.yaml")));
120     }
121 
122     @Test
123     public void communicatesFileResourceModification() throws Exception {
124         // GIVEN
125         // create a node
126         final Node barFileNode = NodeUtil.createPath(session.getRootNode(), "foo/dialogs/bar.yaml", NodeTypes.ContentNode.NAME, true);
127 
128         // register a resource change handler
129         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
130         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
131 
132         // WHEN we create a file node
133         barFileNode.setProperty("qux", "quux");
134         session.save();
135 
136         // THEN handler is notified with a correct change type, path and related origin
137         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(MODIFIED).at("/foo/dialogs/bar.yaml")));
138 
139         assertNotEquals("Original session is released", session, MgnlContext.getJCRSession(RESOURCES_WORKSPACE));
140     }
141 
142     @Test
143     public void communicatesFileResourceDeletion() throws Exception {
144         // GIVEN
145         // create a node
146         final Node barFileNode = NodeUtil.createPath(session.getRootNode(), "foo/dialogs/bar.yaml", NodeTypes.ContentNode.NAME, true);
147 
148         // register a resource change handler
149         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
150         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
151 
152         // WHEN we create a file node
153         barFileNode.remove();
154         session.save();
155 
156         // THEN handler is notified with a correct change type, path and related origin
157         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs/bar.yaml")));
158 
159         assertNotEquals("Original session is released", session, MgnlContext.getJCRSession(RESOURCES_WORKSPACE));
160     }
161 
162     @Test
163     public void communicatesResourceFolderAdditionWithContents() throws Exception {
164         // GIVEN
165         // register a resource change handler
166         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
167         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
168 
169         // WHEN we create a file node
170         final Node fooDialogs = NodeUtil.createPath(session.getRootNode(), "foo/dialogs", NodeTypes.Folder.NAME);
171         NodeUtil.createPath(fooDialogs, "bar.yaml", NodeTypes.ContentNode.NAME);
172         NodeUtil.createPath(fooDialogs, "qux.yaml", NodeTypes.ContentNode.NAME);
173         session.save();
174 
175         // THEN handler is notified with a correct change type, path and related origin
176         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(ADDED).at("/foo/dialogs")));
177         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(ADDED).at("/foo/dialogs/bar.yaml")));
178         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(ADDED).at("/foo/dialogs/bar.yaml")));
179     }
180 
181     @Test
182     public void communicatesResourceFolderDeletionWithContents() throws Exception {
183         // GIVEN
184         // create a folder and a couple of sub-nodes
185         final Node fooDialogs = NodeUtil.createPath(session.getRootNode(), "foo/dialogs", NodeTypes.Folder.NAME, true);
186         NodeUtil.createPath(fooDialogs, "bar.yaml", NodeTypes.ContentNode.NAME, true);
187         NodeUtil.createPath(fooDialogs, "qux.yaml", NodeTypes.ContentNode.NAME, true);
188 
189         // register a resource change handler
190         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
191         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
192 
193         // WHEN we create a file node
194         fooDialogs.remove();
195         session.save();
196 
197         // THEN handler is notified with a correct change type, path and related origin
198         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs")));
199         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs/bar.yaml")));
200         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs/bar.yaml")));
201     }
202 
203     @Test
204     public void bypassSetToTrueTreatedAsResourceRemoval() throws Exception {
205         // GIVEN
206         // create a couple of nodes
207         final Node barFileNode = NodeUtil.createPath(session.getRootNode(), "foo/dialogs/bar.yaml", NodeTypes.ContentNode.NAME);
208         final Node bazFileNode = NodeUtil.createPath(session.getRootNode(), "foo/dialogs/baz.yaml", NodeTypes.ContentNode.NAME);
209         barFileNode.setProperty("bypass", false);
210         session.save();
211 
212         // WHEN register a resource change handler
213         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
214         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
215 
216         // newly set/added property for baz
217         bazFileNode.setProperty("bypass", true);
218         // modified property for bar
219         barFileNode.setProperty("bypass", true);
220         session.save();
221 
222         // THEN
223         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs/bar.yaml")));
224         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(REMOVED).at("/foo/dialogs/baz.yaml")));
225 
226         assertNotEquals("Original session is released", session, MgnlContext.getJCRSession(RESOURCES_WORKSPACE));
227     }
228 
229     @Test
230     public void bypassSetToFalseTreatedAsResourceAddition() throws Exception {
231         // GIVEN
232         // create a bypassed node
233         final Node barFileNode = NodeUtil.createPath(session.getRootNode(), "foo/dialogs/bar.yaml", NodeTypes.ContentNode.NAME);
234         barFileNode.setProperty("bypass", true);
235         session.save();
236 
237         // WHEN register a resource change handler
238         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
239         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
240 
241         barFileNode.setProperty("bypass", false);
242         session.save();
243 
244         // THEN
245         verify(resourceChangeHandler, timeout(TIMEOUT)).onResourceChanged(argThat(resourceChange().inOrigin(jcrOrigin).ofType(ADDED).at("/foo/dialogs/bar.yaml")));
246     }
247 
248     @Test
249     public void ignoreChangesWhenResourceIsBypassed() throws Exception {
250         // Given
251         Node folder = NodeUtil.createPath(session.getRootNode(), "myModule/dialogs", NodeTypes.Folder.NAME);
252         Node file = folder.addNode("panda.yaml", NodeTypes.Content.NAME);
253         file.setProperty(BYPASS_PROPERTY, true);
254         session.save();
255 
256 
257         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
258         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
259 
260         // When
261         file.setProperty("text", "<DUMMY CONTENT>");
262         session.save();
263 
264         // Then
265         verify(resourceChangeHandler, after(TIMEOUT).never()).onResourceChanged(anyObject());
266     }
267 
268     @Test
269     public void ignoreChangesOnSharedSystemNodesWhenWatchingResourcesRoot() throws Exception {
270         // Given we observe root of the resources workspace
271         final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
272         jcrOrigin.registerResourceChangeHandler(resourceChangeHandler);
273 
274         // versioning typically writes changes in the shared /jcr:system sub-tree, i.e. observation may get events from different workspaces
275         Session websiteSession = MgnlContext.getJCRSession(WEBSITE);
276         websiteSession.getRootNode().addNode("b", NodeTypes.Content.NAME);
277         websiteSession.save();
278         VersionManager versionManager = ((DelegateSessionWrapper) websiteSession).unwrap().getWorkspace().getVersionManager();
279 
280         // When
281         websiteSession.getNode("/b").addMixin(NodeType.MIX_VERSIONABLE);
282         websiteSession.save();
283         versionManager.checkin("/b");
284 
285         // Then
286         // Fail gracefully if after() interrupts test and shuts down repo while the deferred listener is still iterating
287         verify(resourceChangeHandler, after(TIMEOUT).never()).onResourceChanged(any(ResourceOriginChange.class));
288         verifyZeroInteractions(resourceChangeHandler);
289     }
290 }