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}