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