001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.http.commons.test.util;
019
020import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
021import static org.apache.http.entity.ContentType.parse;
022import static java.net.URI.create;
023import static java.nio.charset.StandardCharsets.UTF_8;
024import static javax.ws.rs.core.UriBuilder.fromUri;
025import static org.fcrepo.kernel.api.utils.ContentDigest.DIGEST_ALGORITHM.SHA1;
026import static org.junit.Assert.assertNotNull;
027import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
028import static org.fcrepo.kernel.api.utils.ContentDigest.asURI;
029import static org.mockito.Matchers.anyString;
030import static org.mockito.Mockito.mock;
031import static org.mockito.Mockito.when;
032
033import java.io.IOException;
034import java.io.InputStream;
035import java.lang.reflect.Field;
036import java.net.URI;
037import java.security.MessageDigest;
038import java.security.NoSuchAlgorithmException;
039import java.security.Principal;
040import java.time.Instant;
041import java.util.Arrays;
042import java.util.Collection;
043
044import javax.jcr.LoginException;
045import javax.jcr.Node;
046import javax.jcr.NodeIterator;
047import javax.jcr.RepositoryException;
048import javax.jcr.Session;
049import javax.jcr.ValueFactory;
050import javax.jcr.Workspace;
051import javax.jcr.nodetype.NodeType;
052import javax.jcr.nodetype.NodeTypeIterator;
053import javax.jcr.nodetype.NodeTypeManager;
054import javax.jcr.query.Query;
055import javax.jcr.query.QueryResult;
056import javax.ws.rs.core.SecurityContext;
057import javax.ws.rs.core.UriBuilder;
058import javax.ws.rs.core.UriInfo;
059
060import org.apache.commons.io.IOUtils;
061import org.apache.http.HttpEntity;
062import org.apache.http.util.EntityUtils;
063import org.apache.jena.riot.Lang;
064
065import org.fcrepo.http.commons.AbstractResource;
066import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
067import org.fcrepo.kernel.api.models.FedoraBinary;
068import org.fcrepo.kernel.api.services.functions.HierarchicalIdentifierSupplier;
069
070import org.mockito.Mockito;
071import org.mockito.invocation.InvocationOnMock;
072import org.mockito.stubbing.Answer;
073import org.modeshape.jcr.api.NamespaceRegistry;
074import org.modeshape.jcr.api.Repository;
075import org.modeshape.jcr.api.query.QueryManager;
076
077import org.apache.jena.rdf.model.Model;
078
079/**
080 * <p>Abstract TestHelpers class.</p>
081 *
082 * @author awoods
083 */
084public abstract class TestHelpers {
085
086    public static String MOCK_PREFIX = "mockPrefix";
087
088    public static String MOCK_URI_STRING = "mock.namespace.org";
089
090    public static UriInfo getUriInfoImpl() {
091        // UriInfo ui = mock(UriInfo.class,withSettings().verboseLogging());
092        final UriInfo ui = mock(UriInfo.class);
093
094        final Answer<UriBuilder> answer = new Answer<UriBuilder>() {
095
096            @Override
097            public UriBuilder answer(final InvocationOnMock invocation) {
098                return fromUri("http://localhost/fcrepo");
099            }
100        };
101
102        when(ui.getRequestUri()).thenReturn(
103                URI.create("http://localhost/fcrepo"));
104        when(ui.getBaseUri()).thenReturn(create("http://localhost/fcrepo"));
105        when(ui.getBaseUriBuilder()).thenAnswer(answer);
106        when(ui.getAbsolutePathBuilder()).thenAnswer(answer);
107
108        return ui;
109    }
110
111    public static Session getQuerySessionMock() {
112        final Session mock = mock(Session.class);
113        final Workspace mockWS = mock(Workspace.class);
114        final QueryManager mockQM = mock(QueryManager.class);
115        try {
116            final Query mockQ = getQueryMock();
117            when(mockQM.createQuery(anyString(), anyString()))
118                    .thenReturn((org.modeshape.jcr.api.query.Query) mockQ);
119            when(mockWS.getQueryManager()).thenReturn(mockQM);
120        } catch (final RepositoryException e) {
121            e.printStackTrace();
122        }
123        when(mock.getWorkspace()).thenReturn(mockWS);
124        final ValueFactory mockVF = mock(ValueFactory.class);
125        try {
126            when(mock.getValueFactory()).thenReturn(mockVF);
127        } catch (final RepositoryException e) {
128            e.printStackTrace();
129        }
130        return mock;
131    }
132
133    @SuppressWarnings("unchecked")
134    public static Query getQueryMock() {
135        final Query mockQ = mock(Query.class);
136        final QueryResult mockResults = mock(QueryResult.class);
137        final NodeIterator mockNodes = mock(NodeIterator.class);
138        when(mockNodes.getSize()).thenReturn(2L);
139        when(mockNodes.hasNext()).thenReturn(true, true, false);
140        final Node node1 = mock(Node.class);
141        final Node node2 = mock(Node.class);
142        try {
143            when(node1.getName()).thenReturn("node1");
144            when(node2.getName()).thenReturn("node2");
145        } catch (final RepositoryException e) {
146            e.printStackTrace();
147        }
148        when(mockNodes.nextNode()).thenReturn(node1, node2).thenThrow(
149                IndexOutOfBoundsException.class);
150        try {
151            when(mockResults.getNodes()).thenReturn(mockNodes);
152            when(mockQ.execute()).thenReturn(mockResults);
153        } catch (final RepositoryException e) {
154            e.printStackTrace();
155        }
156        return mockQ;
157    }
158
159    @SuppressWarnings("unchecked")
160    public static Session getSessionMock() throws RepositoryException {
161        final String[] mockPrefixes = {MOCK_PREFIX};
162        final Session mockSession = mock(Session.class);
163        final Workspace mockWorkspace = mock(Workspace.class);
164        final NamespaceRegistry mockNameReg = mock(NamespaceRegistry.class);
165        final NodeTypeManager mockNTM = mock(NodeTypeManager.class);
166        final NodeTypeIterator mockNTI = mock(NodeTypeIterator.class);
167        final NodeType mockNodeType = mock(NodeType.class);
168        when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
169        when(mockWorkspace.getNamespaceRegistry()).thenReturn(mockNameReg);
170        when(mockNameReg.getPrefixes()).thenReturn(mockPrefixes);
171        when(mockNameReg.getURI(MOCK_PREFIX)).thenReturn(MOCK_URI_STRING);
172        when(mockWorkspace.getNodeTypeManager()).thenReturn(mockNTM);
173        when(mockNodeType.getName()).thenReturn("mockName");
174        when(mockNodeType.toString()).thenReturn("mockString");
175        when(mockNTM.getAllNodeTypes()).thenReturn(mockNTI);
176        when(mockNTI.hasNext()).thenReturn(true, false);
177        when(mockNTI.nextNodeType()).thenReturn(mockNodeType).thenThrow(
178                ArrayIndexOutOfBoundsException.class);
179        return mockSession;
180    }
181
182    public static Collection<String>
183            parseChildren(final HttpEntity entity) throws IOException {
184        final String body = EntityUtils.toString(entity);
185        System.err.println(body);
186        final String[] names =
187            body.replace("[", "").replace("]", "").trim().split(",\\s?");
188        return Arrays.asList(names);
189    }
190
191    public static Session mockSession(final AbstractResource testObj) {
192
193        final SecurityContext mockSecurityContext = mock(SecurityContext.class);
194        final Principal mockPrincipal = mock(Principal.class);
195
196        final String mockUser = "testuser";
197
198        final Session mockSession = mock(Session.class);
199        when(mockSession.getUserID()).thenReturn(mockUser);
200        when(mockSecurityContext.getUserPrincipal()).thenReturn(mockPrincipal);
201        when(mockPrincipal.getName()).thenReturn(mockUser);
202
203        final Workspace mockWorkspace = mock(Workspace.class);
204        when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
205        when(mockWorkspace.getName()).thenReturn("default");
206
207        setField(testObj, "uriInfo", getUriInfoImpl());
208        setField(testObj, "pidMinter", new PathMinterImpl());
209        return mockSession;
210
211    }
212
213    public static Repository mockRepository() throws LoginException,
214                                             RepositoryException {
215        final Repository mockRepo = mock(Repository.class);
216        when(mockRepo.login()).thenReturn(
217                mock(org.modeshape.jcr.api.Session.class, Mockito.withSettings().extraInterfaces(Session.class)));
218        return mockRepo;
219    }
220
221    public static NonRdfSourceDescription mockDatastream(final String pid,
222                                            final String dsId, final String content) {
223        final FedoraBinary mockBinary = mockBinary(pid, dsId, content);
224        final NonRdfSourceDescription mockDs = mock(NonRdfSourceDescription.class);
225        when(mockDs.getDescribedResource()).thenReturn(mockBinary);
226        when(mockDs.getDescribedResource().getDescription()).thenReturn(mockDs);
227        when(mockDs.getPath()).thenReturn("/" + pid + "/" + dsId);
228        when(mockDs.getCreatedDate()).thenReturn(Instant.now());
229        when(mockDs.getLastModifiedDate()).thenReturn(Instant.now());
230        if (content != null) {
231            final MessageDigest md;
232            try {
233                md = MessageDigest.getInstance(SHA1.algorithm);
234            } catch (final NoSuchAlgorithmException e) {
235                throw new RuntimeException(e);
236            }
237            final byte[] digest = md.digest(content.getBytes());
238            final URI cd = asURI(SHA1.algorithm, digest);
239            when(mockDs.getEtagValue()).thenReturn(cd.toString());
240        }
241        return mockDs;
242    }
243
244    public static FedoraBinary mockBinary(final String pid,
245                                          final String dsId, final String content) {
246        final FedoraBinary mockBinary = mock(FedoraBinary.class);
247        when(mockBinary.getPath()).thenReturn("/" + pid + "/" + dsId + "/jcr:content");
248        when(mockBinary.getMimeType()).thenReturn("application/octet-stream");
249        when(mockBinary.getCreatedDate()).thenReturn(Instant.now());
250        when(mockBinary.getLastModifiedDate()).thenReturn(Instant.now());
251        if (content != null) {
252            final MessageDigest md;
253            try {
254                md = MessageDigest.getInstance(SHA1.algorithm);
255            } catch (final NoSuchAlgorithmException e) {
256                throw new RuntimeException(e);
257            }
258            final byte[] digest = md.digest(content.getBytes());
259            final URI cd = asURI(SHA1.algorithm, digest);
260            when(mockBinary.getContent()).thenReturn(
261                    IOUtils.toInputStream(content, UTF_8));
262            when(mockBinary.getContentDigest()).thenReturn(cd);
263            when(mockBinary.getEtagValue()).thenReturn(cd.toString());
264        }
265        return mockBinary;
266    }
267
268    private static String getRdfSerialization(final HttpEntity entity) {
269        final Lang lang = contentTypeToLang(parse(entity.getContentType().getValue()).getMimeType());
270        assertNotNull("Entity is not an RDF serialization", lang);
271        return lang.getName();
272    }
273
274    public static CloseableDataset parseTriples(final HttpEntity entity) throws IOException {
275        return parseTriples(entity.getContent(), getRdfSerialization(entity));
276    }
277
278    public static CloseableDataset parseTriples(final InputStream content) {
279        return parseTriples(content, "N3");
280    }
281
282    public static CloseableDataset parseTriples(final InputStream content, final String contentType) {
283        final Model model = createDefaultModel();
284        model.read(content, "", contentType);
285        return new CloseableDataset(model);
286    }
287
288    /**
289     * Set a field via reflection
290     *
291     * @param parent the owner object of the field
292     * @param name the name of the field
293     * @param obj the value to set
294     */
295    public static void setField(final Object parent, final String name, final Object obj) {
296        /* check the parent class too if the field could not be found */
297        try {
298            final Field f = findField(parent.getClass(), name);
299            f.setAccessible(true);
300            f.set(parent, obj);
301        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
302            throw new RuntimeException(e);
303        }
304    }
305
306    private static Field findField(final Class<?> clazz, final String name)
307        throws NoSuchFieldException {
308        for (final Field f : clazz.getDeclaredFields()) {
309            if (f.getName().equals(name)) {
310                return f;
311            }
312        }
313        if (clazz.getSuperclass() == null) {
314            throw new NoSuchFieldException("Field " + name
315                    + " could not be found");
316        }
317        return findField(clazz.getSuperclass(), name);
318    }
319
320    private static class PathMinterImpl implements HierarchicalIdentifierSupplier { }
321}