001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.client;
007
008import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE;
009import static org.fcrepo.client.FedoraHeaderConstants.DIGEST;
010import static org.fcrepo.client.FedoraHeaderConstants.IF_MATCH;
011import static org.fcrepo.client.FedoraHeaderConstants.IF_STATE_TOKEN;
012import static org.fcrepo.client.FedoraHeaderConstants.IF_UNMODIFIED_SINCE;
013import static org.fcrepo.client.FedoraHeaderConstants.LINK;
014import static org.fcrepo.client.LinkHeaderConstants.ACL_REL;
015import static org.fcrepo.client.LinkHeaderConstants.EXTERNAL_CONTENT_HANDLING;
016import static org.fcrepo.client.LinkHeaderConstants.EXTERNAL_CONTENT_REL;
017import static org.fcrepo.client.LinkHeaderConstants.TYPE_REL;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.URI;
024import java.util.StringJoiner;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
028import org.apache.http.entity.InputStreamEntity;
029import org.fcrepo.client.FcrepoLink.Builder;
030
031/**
032 * Request builder which includes a body component
033 *
034 * @author bbpennel
035 */
036public abstract class BodyRequestBuilder extends
037        RequestBuilder {
038
039    private StringJoiner digestJoiner;
040
041    /**
042     * Instantiate builder
043     *
044     * @param uri uri request will be issued to
045     * @param client the client
046     */
047    protected BodyRequestBuilder(final URI uri, final FcrepoClient client) {
048        super(uri, client);
049    }
050
051    /**
052     * Add a body to this request from a stream, with application/octet-stream as its content type
053     *
054     * @param stream InputStream of the content to be sent to the server
055     * @return this builder
056     */
057    protected BodyRequestBuilder body(final InputStream stream) {
058        return body(stream, null);
059    }
060
061    /**
062     * Add a body to this request as a stream with the given content type
063     *
064     * @param stream InputStream of the content to be sent to the server
065     * @param contentType the Content-Type of the body
066     * @return this builder
067     */
068    protected BodyRequestBuilder body(final InputStream stream, final String contentType) {
069        if (stream != null) {
070            String type = contentType;
071            if (type == null) {
072                type = "application/octet-stream";
073            }
074
075            ((HttpEntityEnclosingRequestBase) request).setEntity(new InputStreamEntity(stream));
076            request.addHeader(CONTENT_TYPE, type);
077        }
078
079        return this;
080    }
081
082    /**
083     * Add the given file as the body for this request with the provided content type
084     *
085     * @param file File containing the content to be sent to the server
086     * @param contentType the Content-Type of the body
087     * @return this builder
088     * @throws IOException when unable to stream the body file
089     */
090    protected BodyRequestBuilder body(final File file, final String contentType) throws IOException {
091        return body(new FileInputStream(file), contentType);
092    }
093
094    /**
095     * Add the given URI to the request as the location a Non-RDF Source binary should use for external content. The
096     * handling parameter must be supplied, and informs the server of how to process the request.
097     *
098     * @param contentURI URI of the external content.
099     * @param contentType Mimetype to supply for the external content.
100     * @param handling Name of the handling method, used by the server to determine how to process the external
101     *        content URI. Standard values can be found in {@link ExternalContentHandling}.
102     * @return this builder
103     */
104    protected BodyRequestBuilder externalContent(final URI contentURI, final String contentType,
105            final String handling) {
106        final Builder linkBuilder = FcrepoLink.fromUri(contentURI)
107            .rel(EXTERNAL_CONTENT_REL)
108            .param(EXTERNAL_CONTENT_HANDLING, handling);
109
110        if (StringUtils.isNotBlank(contentType)) {
111            linkBuilder.type(contentType);
112        }
113
114        request.addHeader(LINK, linkBuilder.build().toString());
115        return this;
116    }
117
118    /**
119     * Provide a SHA-1 checksum for the body of this request.
120     *
121     * @deprecated Use {@link #digestSha1(java.lang.String)}.
122     * @param digest sha-1 checksum to provide as the digest for the request body
123     * @return this builder
124     */
125    @Deprecated
126    protected BodyRequestBuilder digest(final String digest) {
127        return digestSha1(digest);
128    }
129
130    /**
131     * Provide a checksum for the body of this request
132     *
133     * @param digest checksum to provide as the digest for the request body
134     * @param alg abbreviated algorithm identifier for the type of checksum being
135     *      added (for example, sha1, md5, etc)
136     * @return this builder
137     */
138    protected BodyRequestBuilder digest(final String digest, final String alg) {
139        if (digest != null) {
140            if (digestJoiner == null) {
141                digestJoiner = new StringJoiner(", ");
142            }
143            digestJoiner.add(alg + "=" + digest);
144            request.setHeader(DIGEST, digestJoiner.toString());
145        }
146        return this;
147    }
148
149    /**
150     * Provide a SHA-1 checksum for the body of this request.
151     *
152     * @param digest sha-1 checksum to provide as the digest for the request body
153     * @return this builder
154     */
155    protected BodyRequestBuilder digestSha1(final String digest) {
156        return digest(digest, "sha");
157    }
158
159    /**
160     * Provide a MD5 checksum for the body of this request
161     *
162     * @param digest MD5 checksum to provide as the digest for the request body
163     * @return this builder
164     */
165    protected BodyRequestBuilder digestMd5(final String digest) {
166        return digest(digest, "md5");
167    }
168
169    /**
170     * Provide a SHA-256 checksum for the body of this request
171     *
172     * @param digest sha-256 checksum to provide as the digest for the request body
173     * @return this builder
174     */
175    protected BodyRequestBuilder digestSha256(final String digest) {
176        return digest(digest, "sha256");
177    }
178
179    /**
180     * Add an interaction model to the request
181     *
182     * @param interactionModelUri URI of the interaction model
183     * @return this builder
184     */
185    protected BodyRequestBuilder addInteractionModel(final String interactionModelUri) {
186        if (interactionModelUri != null) {
187            final FcrepoLink link = FcrepoLink.fromUri(interactionModelUri)
188                    .rel(TYPE_REL)
189                    .build();
190            request.addHeader(LINK, link.toString());
191        }
192        return this;
193    }
194
195    /**
196     * Provide a if-unmodified-since header for this request
197     *
198     * @param modified date to provide as the if-unmodified-since header
199     * @return this builder
200     */
201    public BodyRequestBuilder ifUnmodifiedSince(final String modified) {
202        if (modified != null) {
203            request.setHeader(IF_UNMODIFIED_SINCE, modified);
204        }
205        return this;
206    }
207
208    /**
209     * Provide an etag for the if-match header for this request
210     *
211     * @param etag etag to provide as the if-match header
212     * @return this builder
213     */
214    protected BodyRequestBuilder ifMatch(final String etag) {
215        if (etag != null) {
216            request.setHeader(IF_MATCH, etag);
217        }
218        return this;
219    }
220
221    /**
222     * Provide the URI to an ACL for this request
223     *
224     * @param aclUri URI to the ACL
225     * @return this builder
226     */
227    protected BodyRequestBuilder linkAcl(final String aclUri) {
228        if (aclUri != null) {
229            final FcrepoLink link = FcrepoLink.fromUri(aclUri)
230                    .rel(ACL_REL)
231                    .build();
232            request.addHeader(LINK, link.toString());
233        }
234        return this;
235    }
236
237    /**
238     * Provide a value for the if-state-token header for this request.
239     *
240     * @param token state token value
241     * @return this builder
242     */
243    protected BodyRequestBuilder ifStateToken(final String token) {
244        if (token != null) {
245            request.setHeader(IF_STATE_TOKEN, token);
246        }
247        return this;
248    }
249}