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.kernel.impl.models; 019 020import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 021import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER; 022import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI; 023import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_WEBAC_ACL_URI; 024import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER; 025import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 026import static org.slf4j.LoggerFactory.getLogger; 027 028import java.time.Instant; 029import java.util.stream.Stream; 030 031import javax.inject.Inject; 032 033import org.fcrepo.kernel.api.ContainmentIndex; 034import org.fcrepo.kernel.api.Transaction; 035import org.fcrepo.kernel.api.exception.PathNotFoundException; 036import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException; 037import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 038import org.fcrepo.kernel.api.exception.ResourceTypeException; 039import org.fcrepo.kernel.api.identifiers.FedoraId; 040import org.fcrepo.kernel.api.models.Binary; 041import org.fcrepo.kernel.api.models.FedoraResource; 042import org.fcrepo.kernel.api.models.ResourceFactory; 043import org.fcrepo.kernel.api.models.ResourceHeaders; 044import org.fcrepo.persistence.api.PersistentStorageSession; 045import org.fcrepo.persistence.api.PersistentStorageSessionManager; 046import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 047import org.fcrepo.persistence.api.exceptions.PersistentStorageException; 048 049import org.slf4j.Logger; 050import org.springframework.beans.factory.annotation.Autowired; 051import org.springframework.beans.factory.annotation.Qualifier; 052import org.springframework.stereotype.Component; 053 054/** 055 * Implementation of ResourceFactory interface. 056 * 057 * @author whikloj 058 * @since 2019-09-23 059 */ 060@Component 061public class ResourceFactoryImpl implements ResourceFactory { 062 063 private static final Logger LOGGER = getLogger(ResourceFactoryImpl.class); 064 065 @Inject 066 private PersistentStorageSessionManager persistentStorageSessionManager; 067 068 @Autowired 069 @Qualifier("containmentIndex") 070 private ContainmentIndex containmentIndex; 071 072 @Override 073 public FedoraResource getResource(final Transaction transaction, final FedoraId fedoraID) 074 throws PathNotFoundException { 075 return instantiateResource(transaction, fedoraID); 076 } 077 078 @Override 079 public <T extends FedoraResource> T getResource(final Transaction transaction, final FedoraId identifier, 080 final Class<T> clazz) throws PathNotFoundException { 081 return clazz.cast(getResource(transaction, identifier)); 082 } 083 084 085 @Override 086 public FedoraResource getContainer(final Transaction transaction, final FedoraId resourceId) { 087 final String containerId = containmentIndex.getContainedBy(transaction, resourceId); 088 if (containerId == null) { 089 return null; 090 } 091 try { 092 return getResource(transaction, FedoraId.create(containerId)); 093 } catch (final PathNotFoundException exc) { 094 return null; 095 } 096 } 097 098 /** 099 * Returns the appropriate FedoraResource class for an object based on the provided headers 100 * 101 * @param headers headers for the resource being constructed 102 * @return FedoraResource class 103 */ 104 private Class<? extends FedoraResourceImpl> getClassForTypes(final ResourceHeaders headers) { 105 final var ixModel = headers.getInteractionModel(); 106 if (BASIC_CONTAINER.getURI().equals(ixModel) || INDIRECT_CONTAINER.getURI().equals(ixModel) 107 || DIRECT_CONTAINER.getURI().equals(ixModel)) { 108 return ContainerImpl.class; 109 } 110 if (NON_RDF_SOURCE.getURI().equals(ixModel)) { 111 return BinaryImpl.class; 112 } 113 if (FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI.equals(ixModel)) { 114 return NonRdfSourceDescriptionImpl.class; 115 } 116 if (FEDORA_WEBAC_ACL_URI.equals(ixModel)) { 117 return WebacAclImpl.class; 118 } 119 // TODO add the rest of the types 120 throw new ResourceTypeException("Could not identify the resource type for interaction model " + ixModel); 121 } 122 123 /** 124 * Instantiates a new FedoraResource object of the given class. 125 * 126 * @param transaction the transaction id 127 * @param identifier identifier for the new instance 128 * @return new FedoraResource instance 129 * @throws PathNotFoundException 130 */ 131 private FedoraResource instantiateResource(final Transaction transaction, 132 final FedoraId identifier) 133 throws PathNotFoundException { 134 try { 135 // For descriptions and ACLs we need the actual endpoint. 136 final var psSession = getSession(transaction); 137 final Instant versionDateTime = identifier.isMemento() ? identifier.getMementoInstant() : null; 138 139 final ResourceHeaders headers = psSession.getHeaders(identifier, versionDateTime); 140 141 // Determine the appropriate class from headers 142 final var createClass = getClassForTypes(headers); 143 144 // Retrieve standard constructor 145 final var constructor = createClass.getConstructor( 146 FedoraId.class, 147 Transaction.class, 148 PersistentStorageSessionManager.class, 149 ResourceFactory.class); 150 151 // If identifier is to a TimeMap we need to avoid creating a original resource with a Timemap FedoraId 152 final var instantiationId = identifier.isTimemap() ? 153 FedoraId.create(identifier.getResourceId()) : identifier; 154 155 final var rescImpl = constructor.newInstance(instantiationId, transaction, 156 persistentStorageSessionManager, this); 157 populateResourceHeaders(rescImpl, headers, versionDateTime); 158 159 if (headers.isDeleted()) { 160 final var rootId = FedoraId.create(identifier.getBaseId()); 161 final var tombstone = new TombstoneImpl(rootId, transaction, persistentStorageSessionManager, 162 this, rescImpl); 163 tombstone.setLastModifiedDate(headers.getLastModifiedDate()); 164 return tombstone; 165 } else if (identifier.isTimemap()) { 166 // If identifier is a TimeMap, now we can return the virtual resource. 167 return rescImpl.getTimeMap(); 168 } 169 return rescImpl; 170 } catch (final SecurityException | ReflectiveOperationException e) { 171 throw new RepositoryRuntimeException("Unable to construct object", e); 172 } catch (final PersistentItemNotFoundException e) { 173 throw new PathNotFoundException(e.getMessage(), e); 174 } catch (final PersistentStorageException e) { 175 throw new RepositoryRuntimeException(e.getMessage(), e); 176 } 177 } 178 179 private void populateResourceHeaders(final FedoraResourceImpl resc, 180 final ResourceHeaders headers, final Instant version) { 181 resc.setCreatedBy(headers.getCreatedBy()); 182 resc.setCreatedDate(headers.getCreatedDate()); 183 resc.setLastModifiedBy(headers.getLastModifiedBy()); 184 resc.setLastModifiedDate(headers.getLastModifiedDate()); 185 resc.setParentId(headers.getParent()); 186 resc.setEtag(headers.getStateToken()); 187 resc.setStateToken(headers.getStateToken()); 188 resc.setIsArchivalGroup(headers.isArchivalGroup()); 189 resc.setInteractionModel(headers.getInteractionModel()); 190 191 // If there's a version, then it's a memento 192 if (version != null) { 193 resc.setIsMemento(true); 194 resc.setMementoDatetime(version); 195 } 196 197 if (resc instanceof Binary) { 198 final var binary = (BinaryImpl) resc; 199 binary.setContentSize(headers.getContentSize()); 200 binary.setExternalHandling(headers.getExternalHandling()); 201 binary.setExternalUrl(headers.getExternalUrl()); 202 binary.setDigests(headers.getDigests()); 203 binary.setFilename(headers.getFilename()); 204 binary.setMimeType(headers.getMimeType()); 205 } 206 } 207 208 /** 209 * Get a session for this interaction. 210 * 211 * @param transaction The supplied transaction. 212 * @return a storage session. 213 */ 214 private PersistentStorageSession getSession(final Transaction transaction) { 215 final PersistentStorageSession session; 216 if (transaction.isReadOnly() || !transaction.isOpen()) { 217 session = persistentStorageSessionManager.getReadOnlySession(); 218 } else { 219 session = persistentStorageSessionManager.getSession(transaction); 220 } 221 return session; 222 } 223 224 @Override 225 public Stream<FedoraResource> getChildren(final Transaction transaction, final FedoraId resourceId) { 226 return containmentIndex.getContains(transaction, resourceId) 227 .map(childId -> { 228 try { 229 return getResource(transaction, FedoraId.create(childId)); 230 } catch (final PathNotFoundException e) { 231 throw new PathNotFoundRuntimeException(e.getMessage(), e); 232 } 233 }); 234 } 235}