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