Package org.apache.sling.superimposing.impl

Source Code of org.apache.sling.superimposing.impl.SuperimposingManagerImplTest

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.superimposing.impl;

import static org.apache.sling.superimposing.SuperimposingResourceProvider.PROP_SUPERIMPOSE_OVERLAYABLE;
import static org.apache.sling.superimposing.SuperimposingResourceProvider.PROP_SUPERIMPOSE_REGISTER_PARENT;
import static org.apache.sling.superimposing.SuperimposingResourceProvider.PROP_SUPERIMPOSE_SOURCE_PATH;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

import org.apache.commons.collections.IteratorUtils;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.superimposing.SuperimposingResourceProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;

@RunWith(MockitoJUnitRunner.class)
public class SuperimposingManagerImplTest {

    @Mock
    private Dictionary<String, Object> componentContextProperties;
    @Mock
    private ComponentContext componentContext;
    @Mock
    private BundleContext bundleContext;
    @Mock
    private ResourceResolverFactory resourceResolverFactory;
    @Mock
    private ResourceResolver resourceResolver;
    @Mock(answer=Answers.RETURNS_DEEP_STUBS)
    private Session session;
    private List<ServiceRegistration> serviceRegistrations = new ArrayList<ServiceRegistration>();

    private SuperimposingManagerImpl underTest;

    private static final String ORIGINAL_PATH = "/root/path1";
    private static final String SUPERIMPOSED_PATH = "/root/path2";
    private static final String OBSERVATION_PATH = "/root";

    @SuppressWarnings("unchecked")
    @Before
    public void setUp() throws LoginException {
        when(componentContext.getBundleContext()).thenReturn(bundleContext);
        when(componentContext.getProperties()).thenReturn(componentContextProperties);
        when(componentContextProperties.get(SuperimposingManagerImpl.OBSERVATION_PATHS_PROPERTY)).thenReturn(new String[] { OBSERVATION_PATH });
        when(resourceResolverFactory.getAdministrativeResourceResolver(any(Map.class))).thenReturn(resourceResolver);
        when(resourceResolver.adaptTo(Session.class)).thenReturn(session);

        // collect a list of all service registrations to validate that they are all unregistered on shutdown
        when(bundleContext.registerService(anyString(), anyObject(), any(Dictionary.class))).thenAnswer(new Answer<ServiceRegistration>() {
            public ServiceRegistration answer(InvocationOnMock invocation) {
                final ServiceRegistration mockRegistration = mock(ServiceRegistration.class);
                serviceRegistrations.add(mockRegistration);
                doAnswer(new Answer() {
                    public Object answer(InvocationOnMock invocation) {
                        return serviceRegistrations.remove(mockRegistration);
                    }
                }).when(mockRegistration).unregister();
                return mockRegistration;
            }
        });

        // simulate absolute path access to properties via session object
        try {
            when(session.itemExists(anyString())).thenAnswer(new Answer<Boolean>() {
                public Boolean answer(InvocationOnMock invocation) throws Throwable {
                    final String absolutePath = (String)invocation.getArguments()[0];
                    final String nodePath = ResourceUtil.getParent(absolutePath);
                    final String propertyName = ResourceUtil.getName(absolutePath);
                    Resource resource = resourceResolver.getResource(nodePath);
                    if (resource!=null) {
                        ValueMap props = resource.adaptTo(ValueMap.class);
                        return props.containsKey(propertyName);
                    }
                    else {
                        return false;
                    }
                }
            });
            when(session.getProperty(anyString())).thenAnswer(new Answer<Property>() {
                public Property answer(InvocationOnMock invocation) throws Throwable {
                    final String absolutePath = (String)invocation.getArguments()[0];
                    final String nodePath = ResourceUtil.getParent(absolutePath);
                    final String propertyName = ResourceUtil.getName(absolutePath);
                    Resource resource = resourceResolver.getResource(nodePath);
                    if (resource!=null) {
                        ValueMap props = resource.adaptTo(ValueMap.class);
                        Object value = props.get(propertyName);
                        if (value==null) {
                            throw new PathNotFoundException();
                        }
                        Property prop = mock(Property.class);
                        when(prop.getName()).thenReturn(propertyName);
                        if (value instanceof String) {
                            when(prop.getString()).thenReturn((String)value);
                        }
                        else if (value instanceof Boolean) {
                            when(prop.getBoolean()).thenReturn((Boolean)value);
                        }
                        return prop;
                    }
                    else {
                        throw new PathNotFoundException();
                    }
                }
            });
        }
        catch (RepositoryException ex) {
            throw new RuntimeException(ex);
        }
    }

    private void initialize(boolean enabled) throws InterruptedException, LoginException, RepositoryException {
        when(componentContextProperties.get(SuperimposingManagerImpl.ENABLED_PROPERTY)).thenReturn(enabled);

        underTest = new SuperimposingManagerImpl().withResourceResolverFactory(resourceResolverFactory);
        underTest.activate(componentContext);

        if (underTest.isEnabled()) {
            // verify observation registration
            verify(session.getWorkspace().getObservationManager()).addEventListener(any(EventListener.class), anyInt(), eq(OBSERVATION_PATH), anyBoolean(), any(String[].class), any(String[].class), anyBoolean());
            // wait until separate initialization thread has finished
            while (!underTest.initialization.isDone()) {
                Thread.sleep(10);
            }
        }
    }

    @After
    public void tearDown() throws RepositoryException {
        underTest.deactivate(componentContext);

        if (underTest.isEnabled()) {
            // verify observation and resource resolver are terminated correctly
            verify(session.getWorkspace().getObservationManager()).removeEventListener(any(EventListener.class));
            verify(resourceResolver).close();
        }

        // make sure all registrations are unregistered on shutdown
        for (ServiceRegistration registration : serviceRegistrations) {
            verify(registration, times(1)).unregister();
        }
    }

    private Resource prepareSuperimposingResource(String superimposedPath, String sourcePath, boolean registerParent, boolean overlayable) {
        Resource resource = mock(Resource.class);
        when(resource.getPath()).thenReturn(superimposedPath);
        ValueMap props = new ValueMapDecorator(new HashMap<String, Object>());
        props.put(PROP_SUPERIMPOSE_SOURCE_PATH, sourcePath);
        props.put(PROP_SUPERIMPOSE_REGISTER_PARENT, registerParent);
        props.put(PROP_SUPERIMPOSE_OVERLAYABLE, overlayable);
        when(resource.adaptTo(ValueMap.class)).thenReturn(props);
        when(resourceResolver.getResource(superimposedPath)).thenReturn(resource);
        return resource;
    }

    private void moveSuperimposedResource(Resource resource, String newPath) {
        String oldPath = resource.getPath();
        when(resource.getPath()).thenReturn(newPath);
        when(resourceResolver.getResource(oldPath)).thenReturn(null);
        when(resourceResolver.getResource(newPath)).thenReturn(resource);
    }

    @Test
    public void testDisabled() throws InterruptedException, LoginException, RepositoryException {
        // make sure that no exception is thrown when service is disabled on activate/deactivate
        initialize(false);

        verifyZeroInteractions(resourceResolverFactory);
        verifyZeroInteractions(bundleContext);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testFindAllSuperimposings() throws InterruptedException, LoginException, RepositoryException {
        // prepare a query that returns one existing superimposed resource
        when(componentContextProperties.get(SuperimposingManagerImpl.FINDALLQUERIES_PROPERTY)).thenReturn("syntax|query");
        when(resourceResolver.findResources("query", "syntax")).then(new Answer<Iterator<Resource>>() {
            public Iterator<Resource> answer(InvocationOnMock invocation) {
                return Arrays.asList(new Resource[] {
                        prepareSuperimposingResource(SUPERIMPOSED_PATH, ORIGINAL_PATH, false, false)
                }).iterator();
            }
        });
        initialize(true);

        // ensure the superimposed resource is detected and registered
        List<SuperimposingResourceProvider> providers = IteratorUtils.toList(underTest.getRegisteredProviders());
        assertEquals(1, providers.size());
        SuperimposingResourceProvider provider = providers.iterator().next();
        assertEquals(SUPERIMPOSED_PATH, provider.getRootPath());
        assertEquals(ORIGINAL_PATH, provider.getSourcePath());
        assertFalse(provider.isOverlayable());
        verify(bundleContext).registerService(anyString(), same(provider), any(Dictionary.class));
    }

    private EventIterator prepareNodeCreateEvent(Resource pResource) throws RepositoryException {
        String resourcePath = pResource.getPath();

        Event nodeEvent = mock(Event.class);
        when(nodeEvent.getType()).thenReturn(Event.NODE_ADDED);
        when(nodeEvent.getPath()).thenReturn(resourcePath);

        Event propertyEvent = mock(Event.class);
        when(propertyEvent.getType()).thenReturn(Event.PROPERTY_ADDED);
        when(propertyEvent.getPath()).thenReturn(resourcePath + "/" + SuperimposingResourceProvider.PROP_SUPERIMPOSE_SOURCE_PATH);

        EventIterator eventIterator = mock(EventIterator.class);
        when(eventIterator.hasNext()).thenReturn(true, true, false);
        when(eventIterator.nextEvent()).thenReturn(nodeEvent, propertyEvent);
        return eventIterator;
    }

    private EventIterator prepareNodeChangeEvent(Resource pResource) throws RepositoryException {
        String resourcePath = pResource.getPath();

        Event propertyEvent = mock(Event.class);
        when(propertyEvent.getType()).thenReturn(Event.PROPERTY_CHANGED);
        when(propertyEvent.getPath()).thenReturn(resourcePath + "/" + SuperimposingResourceProvider.PROP_SUPERIMPOSE_SOURCE_PATH);

        EventIterator eventIterator = mock(EventIterator.class);
        when(eventIterator.hasNext()).thenReturn(true, false);
        when(eventIterator.nextEvent()).thenReturn(propertyEvent);
        return eventIterator;
    }

    private EventIterator prepareNodeRemoveEvent(Resource pResource) throws RepositoryException {
        String resourcePath = pResource.getPath();

        Event nodeEvent = mock(Event.class);
        when(nodeEvent.getType()).thenReturn(Event.NODE_REMOVED);
        when(nodeEvent.getPath()).thenReturn(resourcePath);

        EventIterator eventIterator = mock(EventIterator.class);
        when(eventIterator.hasNext()).thenReturn(true, false);
        when(eventIterator.nextEvent()).thenReturn(nodeEvent);
        return eventIterator;
    }

    private EventIterator prepareNodeMoveEvent(Resource pResource, String pOldPath) throws RepositoryException {
        String resourcePath = pResource.getPath();

        Event nodeRemoveEvent = mock(Event.class);
        when(nodeRemoveEvent.getType()).thenReturn(Event.NODE_REMOVED);
        when(nodeRemoveEvent.getPath()).thenReturn(pOldPath);

        Event nodeCreateEvent = mock(Event.class);
        when(nodeCreateEvent.getType()).thenReturn(Event.NODE_ADDED);
        when(nodeCreateEvent.getPath()).thenReturn(resourcePath);

        EventIterator eventIterator = mock(EventIterator.class);
        when(eventIterator.hasNext()).thenReturn(true, true, false);
        when(eventIterator.nextEvent()).thenReturn(nodeRemoveEvent, nodeCreateEvent);
        return eventIterator;
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSuperimposedResourceCreateUpdateRemove() throws InterruptedException, LoginException, RepositoryException {
        initialize(true);

        // simulate node create event
        Resource superimposedResource = prepareSuperimposingResource(SUPERIMPOSED_PATH, ORIGINAL_PATH, false, false);
        underTest.onEvent(prepareNodeCreateEvent(superimposedResource));

        // ensure the superimposed resource is detected and registered
        List<SuperimposingResourceProvider> providers = IteratorUtils.toList(underTest.getRegisteredProviders());
        assertEquals(1, providers.size());
        SuperimposingResourceProvider provider = providers.iterator().next();
        assertEquals(SUPERIMPOSED_PATH, provider.getRootPath());
        assertEquals(ORIGINAL_PATH, provider.getSourcePath());
        assertFalse(provider.isOverlayable());
        verify(bundleContext).registerService(anyString(), same(provider), any(Dictionary.class));

        // simulate a change in the original path
        superimposedResource.adaptTo(ValueMap.class).put(PROP_SUPERIMPOSE_SOURCE_PATH, "/other/path");
        underTest.onEvent(prepareNodeChangeEvent(superimposedResource));

        // ensure the superimposed resource update is detected and a new provider instance is registered
        providers = IteratorUtils.toList(underTest.getRegisteredProviders());
        assertEquals(1, providers.size());
        SuperimposingResourceProvider provider2 = providers.iterator().next();
        assertEquals(SUPERIMPOSED_PATH, provider2.getRootPath());
        assertEquals("/other/path", provider2.getSourcePath());
        assertFalse(provider2.isOverlayable());
        verify(bundleContext).registerService(anyString(), same(provider2), any(Dictionary.class));

        // simulate node removal
        underTest.onEvent(prepareNodeRemoveEvent(superimposedResource));

        // ensure provider is removed
        providers = IteratorUtils.toList(underTest.getRegisteredProviders());
        assertEquals(0, providers.size());
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSuperimposedResourceCreateMove() throws InterruptedException, LoginException, RepositoryException {
        when(componentContextProperties.get(SuperimposingManagerImpl.FINDALLQUERIES_PROPERTY)).thenReturn("syntax|query");
        initialize(true);

        // simulate node create event
        final Resource superimposedResource = prepareSuperimposingResource(SUPERIMPOSED_PATH, ORIGINAL_PATH, false, false);
        underTest.onEvent(prepareNodeCreateEvent(superimposedResource));

        // simulate a node move event
        String oldPath = superimposedResource.getPath();
        moveSuperimposedResource(superimposedResource, "/new/path");

        // prepare a query that returns the moved superimposed resource
        when(resourceResolver.findResources("query", "syntax")).then(new Answer<Iterator<Resource>>() {
            public Iterator<Resource> answer(InvocationOnMock invocation) {
                return Arrays.asList(new Resource[] {
                        superimposedResource
                }).iterator();
            }
        });

        underTest.onEvent(prepareNodeMoveEvent(superimposedResource, oldPath));

        // ensure the superimposed resource update is detected and a new provider instance is registered
        List<SuperimposingResourceProvider> providers = IteratorUtils.toList(underTest.getRegisteredProviders());
        assertEquals(1, providers.size());
        SuperimposingResourceProvider provider = providers.iterator().next();
        assertEquals("/new/path", provider.getRootPath());
        assertEquals(ORIGINAL_PATH, provider.getSourcePath());
        assertFalse(provider.isOverlayable());
        verify(bundleContext).registerService(anyString(), same(provider), any(Dictionary.class));
    }

}
TOP

Related Classes of org.apache.sling.superimposing.impl.SuperimposingManagerImplTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.