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.modeshape; 019 020import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring; 021import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_DESCRIPTION; 022import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.FIELD_DELIMITER; 023import static org.fcrepo.kernel.modeshape.services.functions.JcrPropertyFunctions.property2values; 024import static org.fcrepo.kernel.api.RdfLexicon.LDPCV_TIME_MAP; 025 026import static org.slf4j.LoggerFactory.getLogger; 027 028import java.net.URI; 029import java.util.Optional; 030 031import javax.jcr.Node; 032import javax.jcr.PathNotFoundException; 033import javax.jcr.Property; 034import javax.jcr.RepositoryException; 035import javax.jcr.Value; 036 037import org.apache.jena.rdf.model.Resource; 038import org.fcrepo.kernel.api.RdfStream; 039import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 040import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 041import org.fcrepo.kernel.api.models.FedoraBinary; 042import org.fcrepo.kernel.api.models.FedoraResource; 043import org.fcrepo.kernel.api.utils.ContentDigest; 044import org.slf4j.Logger; 045 046/** 047 * Abstract class representing the content of a binary resource 048 * 049 * @author bbpennel 050 */ 051public abstract class AbstractFedoraBinary extends FedoraResourceImpl implements FedoraBinary { 052 053 private static final Logger LOGGER = getLogger(AbstractFedoraBinary.class); 054 055 protected static final String DEFAULT_MIME_TYPE = "application/octet-stream"; 056 057 protected AbstractFedoraBinary(final Node node) { 058 super(node); 059 } 060 061 @Override 062 public FedoraResource getDescription() { 063 final Node descNode = getDescriptionNodeOrNull(); 064 if (descNode == null) { 065 return null; 066 } 067 return new NonRdfSourceDescriptionImpl(getDescriptionNode()); 068 } 069 070 @Override 071 protected Node getDescriptionNode() { 072 073 try { 074 final Node node = getNode(); 075 if (isMemento()) { 076 final String mementoName = node.getName(); 077 return node.getParent().getParent().getNode(FEDORA_DESCRIPTION) 078 .getNode(LDPCV_TIME_MAP).getNode(mementoName); 079 } 080 return getNode().getNode(FEDORA_DESCRIPTION); 081 } catch (final RepositoryException e) { 082 083 // ignore error as Desc memento may not be there yet 084 if (isMemento()) { 085 return null; 086 } 087 throw new RepositoryRuntimeException(e); 088 } 089 } 090 091 protected Node getDescriptionNodeOrNull() { 092 try { 093 return getDescriptionNode(); 094 } catch (final RepositoryRuntimeException e) { 095 if (e.getCause() instanceof PathNotFoundException) { 096 return null; 097 } 098 throw new RepositoryRuntimeException(e); 099 } 100 } 101 102 103 /* 104 * (non-Javadoc) 105 * @see org.fcrepo.kernel.api.models.FedoraBinary#getContentSize() 106 */ 107 @Override 108 public long getContentSize() { 109 try { 110 if (hasDescriptionProperty(CONTENT_SIZE)) { 111 return getDescriptionProperty(CONTENT_SIZE).getLong(); 112 } 113 } catch (final RepositoryException e) { 114 LOGGER.warn("Could not get contentSize(): {}", e.getMessage()); 115 } 116 return -1L; 117 } 118 119 /* 120 * (non-Javadoc) 121 * @see org.fcrepo.kernel.api.models.FedoraBinary#getContentDigest() 122 */ 123 @Override 124 public URI getContentDigest() { 125 126 LOGGER.debug("getContentDigest getting digest info"); 127 try { 128 // Determine which digest algorithm to use 129 final String algorithm = hasDescriptionProperty(DEFAULT_DIGEST_ALGORITHM) ? property2values.apply( 130 getDescriptionProperty(DEFAULT_DIGEST_ALGORITHM)).findFirst().get().getString() 131 : ContentDigest.DEFAULT_ALGORITHM; 132 final String algorithmWithoutStringType = algorithm.replace(FIELD_DELIMITER + XSDstring.getURI(), ""); 133 134 if (hasDescriptionProperty(CONTENT_DIGEST)) { 135 // Select the stored digest that matches the digest algorithm 136 final Optional<Value> digestValue = property2values.apply(getDescriptionProperty(CONTENT_DIGEST)) 137 .filter(digest -> { 138 try { 139 final URI digestUri = URI.create(digest.getString()); 140 return algorithmWithoutStringType.equalsIgnoreCase(ContentDigest.getAlgorithm(digestUri)); 141 142 } catch (final RepositoryException e) { 143 LOGGER.warn("Exception thrown when getting digest property {}, {}", digest, e.getMessage()); 144 return false; 145 } 146 }).findFirst(); 147 148 // Success, return the digest value 149 if (digestValue.isPresent()) { 150 return URI.create(digestValue.get().getString()); 151 } 152 } 153 LOGGER.warn("No digest value was found to match the algorithm: {}", algorithmWithoutStringType); 154 } catch (final RepositoryException e) { 155 LOGGER.warn("Could not get content digest: {}", e.getMessage()); 156 } 157 158 return ContentDigest.missingChecksum(); 159 } 160 161 162 /* 163 * (non-Javadoc) 164 * @see org.fcrepo.kernel.api.models.FedoraBinary#isProxy() 165 */ 166 @Override 167 public Boolean isProxy() { 168 return hasProperty(PROXY_FOR); 169 } 170 171 /* 172 * (non-Javadoc) 173 * @see org.fcrepo.kernel.api.models.FedoraBinary#isRedirect() 174 */ 175 @Override 176 public Boolean isRedirect() { 177 return hasProperty(REDIRECTS_TO); 178 } 179 180 /* 181 * (non-Javadoc) 182 * @see org.fcrepo.kernel.api.models.FedoraBinary#getProxyURL() 183 */ 184 @Override 185 public String getProxyURL() { 186 try { 187 if (hasProperty(PROXY_FOR)) { 188 return getProperty(PROXY_FOR).getString(); 189 } 190 return null; 191 } catch (final RepositoryException e) { 192 throw new RepositoryRuntimeException(e); 193 } 194 } 195 196 /* 197 * (non-Javadoc) 198 * @see org.fcrepo.kernel.api.models.FedoraBinary#setProxyURL() 199 */ 200 @Override 201 public void setProxyURL(final String url) throws RepositoryRuntimeException { 202 try { 203 getNode().setProperty(PROXY_FOR, url); 204 // clear redirect property, in case it used to be a redirect 205 getNode().setProperty(REDIRECTS_TO, (Value) null); 206 } catch (final RepositoryException e) { 207 throw new RepositoryRuntimeException(e); 208 } 209 } 210 211 /* 212 * (non-Javadoc) 213 * @see org.fcrepo.kernel.api.models.FedoraBinary#getRedirectURL() 214 */ 215 @Override 216 public String getRedirectURL() { 217 try { 218 if (hasProperty(REDIRECTS_TO)) { 219 return getProperty(REDIRECTS_TO).getString(); 220 } 221 return null; 222 } catch (final RepositoryException e) { 223 throw new RepositoryRuntimeException(e); 224 } 225 } 226 227 /* 228 * (non-Javadoc) 229 * @see org.fcrepo.kernel.api.models.FedoraBinary#setRedirectURL() 230 */ 231 @Override 232 public void setRedirectURL(final String url) throws RepositoryRuntimeException { 233 try { 234 getNode().setProperty(REDIRECTS_TO, url); 235 // clear proxy property in case it used to be a proxy 236 getNode().setProperty(PROXY_FOR, (Value) null); 237 } catch (final RepositoryException e) { 238 throw new RepositoryRuntimeException(e); 239 } 240 } 241 242 protected String getMimeTypeValue() { 243 try { 244 if (hasDescriptionProperty(HAS_MIME_TYPE)) { 245 return getDescriptionProperty(HAS_MIME_TYPE).getString() 246 .replace(FIELD_DELIMITER + XSDstring.getURI(), ""); 247 } 248 } catch (final RepositoryRuntimeException e) { 249 if (!(e.getCause() instanceof PathNotFoundException) || !isMemento()) { 250 throw e; 251 } 252 } catch (final RepositoryException e) { 253 throw new RepositoryRuntimeException(e); 254 } 255 return DEFAULT_MIME_TYPE; 256 } 257 258 /* 259 * (non-Javadoc) 260 * @see org.fcrepo.kernel.api.models.FedoraBinary#getFilename() 261 */ 262 @Override 263 public String getFilename() { 264 try { 265 if (hasDescriptionProperty(FILENAME)) { 266 return getDescriptionProperty(FILENAME).getString().replace(FIELD_DELIMITER + XSDstring.getURI(), ""); 267 } 268 return node.getName(); 269 } catch (final RepositoryException e) { 270 throw new RepositoryRuntimeException(e); 271 } 272 } 273 274 @Override 275 public RdfStream getFixity(final IdentifierConverter<Resource, FedoraResource> idTranslator) { 276 return getFixity(idTranslator, getContentDigest(), getContentSize()); 277 } 278 279 /** 280 * When deleting the binary, we also need to clean up the description document. 281 */ 282 @Override 283 public void delete() { 284 final FedoraResource description = getDescription(); 285 286 if (description != null) { 287 description.delete(); 288 } 289 290 super.delete(); 291 } 292 293 /** 294 * Check of the property exists on the description of this binary. 295 * 296 * @param relPath - path to the property 297 * @return true if property exists. 298 */ 299 protected boolean hasDescriptionProperty(final String relPath) { 300 301 try { 302 final Node descNode = getDescriptionNodeOrNull(); 303 if (descNode == null) { 304 return false; 305 } 306 return descNode.hasProperty(relPath); 307 } catch (final RepositoryException e) { 308 throw new RepositoryRuntimeException(e); 309 } 310 } 311 312 /** 313 * Return the description property for this binary. 314 * 315 * @param relPath - path to the property 316 * @return Property object 317 */ 318 private Property getDescriptionProperty(final String relPath) { 319 try { 320 return getDescriptionNode().getProperty(relPath); 321 } catch (final RepositoryException e) { 322 throw new RepositoryRuntimeException(e); 323 } 324 } 325 326 /** 327 * Set the content size 328 * 329 * @param size the new value of the content size. 330 */ 331 protected void setContentSize(final long size) { 332 try { 333 getDescriptionNode().setProperty(CONTENT_SIZE, size); 334 } catch (final RepositoryException e) { 335 throw new RepositoryRuntimeException(e); 336 } 337 } 338}