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