001/**
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.migration.f4clients;
017
018import java.io.ByteArrayInputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.UnsupportedEncodingException;
022import java.net.MalformedURLException;
023import java.net.URI;
024import java.net.URISyntaxException;
025
026import org.apache.http.client.methods.CloseableHttpResponse;
027import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
028import org.apache.http.impl.client.CloseableHttpClient;
029import org.fcrepo.client.FcrepoClient;
030import org.fcrepo.client.FcrepoHttpClientBuilder;
031import org.fcrepo.client.FcrepoOperationFailedException;
032import org.fcrepo.client.FcrepoResponse;
033import org.fcrepo.client.HttpMethods;
034import org.fcrepo.migration.Fedora4Client;
035import org.slf4j.Logger;
036
037import static org.slf4j.LoggerFactory.getLogger;
038
039/**
040 * A Fedora4Client implementation that uses the code from the
041 * fcrepo-camel project when possible.
042 * This client is meant to be minimal in that it makes as few
043 * requests as possible to implement the interface.
044 * @author mdurbin
045 */
046public class StatelessFedora4Client implements Fedora4Client {
047
048    private static final Logger LOGGER = getLogger(StatelessFedora4Client.class);
049
050    private String baseUri;
051
052    private String username;
053
054    private String password;
055
056    /**
057     * Constructor for repositories for which Authentication is disabled;
058     * requires the base URL for the Fedora 4 repository.
059     * @param fcrepoBaseURL the base URL for a Fedora 4 repository.
060     */
061    public StatelessFedora4Client(final String fcrepoBaseURL) {
062        baseUri = fcrepoBaseURL;
063    }
064
065    /**
066     * Constructor for repositories for which Authentication is not disabled;
067     * requires the base URL for the Fedora 4 repository.
068     * @param username the username to authenticate with.
069     * @param password the password to authenticate with.
070     * @param fcrepoBaseURL the base URL for a Fedora 4 repository.
071     */
072    public StatelessFedora4Client(final String username, final String password, final String fcrepoBaseURL) {
073        baseUri = fcrepoBaseURL;
074        this.username = username;
075        this.password = password;
076    }
077
078    private FcrepoClient getClient() {
079        try {
080            return new FcrepoClient.FcrepoClientBuilder()
081                    .credentials(username, password)
082                    .authScope(new URI(baseUri).toURL().getHost()).build();
083        } catch (MalformedURLException | URISyntaxException e) {
084            throw new RuntimeException(e);
085        }
086    }
087
088    private boolean success(final FcrepoResponse r) {
089        return r.getStatusCode() >= 200 && r.getStatusCode() < 300;
090    }
091
092    private void assertSuccess(final FcrepoResponse r) {
093        if (!success(r)) {
094            throw new RuntimeException("error code " + r.getStatusCode() + " from request " + r.getUrl());
095        }
096    }
097
098    private URI pathToURI(final String path) throws URISyntaxException {
099        return path.startsWith(baseUri) ? new URI(path) : new URI(baseUri + path);
100    }
101
102    private String uriToPath(final String URI) {
103        if (URI.startsWith(baseUri)) {
104            return URI.substring(baseUri.length());
105        }
106        return URI;
107    }
108
109    @Override
110    public boolean exists(final String path) {
111        try {
112            return getClient().head(pathToURI(path)).perform().getStatusCode() != 404;
113        } catch (FcrepoOperationFailedException | URISyntaxException e) {
114            throw new RuntimeException(e);
115        }
116    }
117
118    @Override
119    public void createResource(final String path) {
120        try {
121            assertSuccess(getClient().put(pathToURI(path)).perform());
122        } catch (FcrepoOperationFailedException | URISyntaxException e) {
123            throw new RuntimeException(e);
124        }
125    }
126
127    @Override
128    public String getRepositoryUrl() {
129        return baseUri.toString();
130    }
131
132    @Override
133    public void createOrUpdateRedirectNonRDFResource(final String path, final String url) {
134        try {
135
136            assertSuccess(getClient().put(pathToURI(path))
137                    .body((InputStream) null, "message/external-body; access-type=URL; URL=\"" + url.toString() + "\"")
138                    .perform());
139        } catch (FcrepoOperationFailedException | URISyntaxException e) {
140            throw new RuntimeException(e);
141        }
142    }
143
144    @Override
145    public void createOrUpdateNonRDFResource(final String path, final InputStream content, final String contentType) {
146        try {
147            assertSuccess(getClient().put(pathToURI(path)).body(content, contentType).perform());
148        } catch (FcrepoOperationFailedException | URISyntaxException e) {
149            throw new RuntimeException(e);
150        } finally {
151            try {
152                content.close();
153            } catch (IOException e) {
154                throw new RuntimeException(e);
155            }
156        }
157
158    }
159
160    @Override
161    public void createVersionSnapshot(final String path,final  String versionId) {
162        try {
163            final FcrepoHttpClientBuilder client
164                    = new FcrepoHttpClientBuilder(username, password, new URI(baseUri).toURL().getHost());
165            try (final CloseableHttpClient c = client.build()) {
166                final HttpMethods method = HttpMethods.POST;
167                final URI uri = pathToURI(path + "/fcr:versions");
168                final HttpEntityEnclosingRequestBase request
169                        = (HttpEntityEnclosingRequestBase) method.createRequest(uri);
170                request.addHeader("Slug", versionId);
171                try (final CloseableHttpResponse response = c.execute(request)) {
172                    final int statusCode = response.getStatusLine().getStatusCode();
173                    if (!(statusCode >= 200 && statusCode < 300)) {
174                        throw new RuntimeException("Unable to create version! " + response.getStatusLine().toString());
175                    }
176                }
177            }
178        } catch (URISyntaxException | IOException e) {
179            throw new RuntimeException(e);
180        }
181    }
182
183    @Override
184    public void updateResourceProperties(final String path, final String sparqlUpdate) {
185        try {
186            assertSuccess(getClient().patch(pathToURI(path))
187                    .body(new ByteArrayInputStream(sparqlUpdate.getBytes("UTF-8"))).perform());
188        } catch (FcrepoOperationFailedException | UnsupportedEncodingException | URISyntaxException e) {
189            throw new RuntimeException(e);
190        }
191    }
192
193    @Override
194    public void updateNonRDFResourceProperties(final String path, final String sparqlUpdate) {
195        try {
196            assertSuccess(getClient().patch(pathToURI(path + "/fcr:metadata"))
197                    .body(new ByteArrayInputStream(sparqlUpdate.getBytes("UTF-8"))).perform());
198        } catch (FcrepoOperationFailedException | UnsupportedEncodingException | URISyntaxException e) {
199            throw new RuntimeException(e);
200        }
201    }
202
203    @Override
204    public String createPlaceholder(final String path) {
205        if (path == null || path.length() == 0) {
206            try {
207                final FcrepoResponse r = getClient().post(new URI(baseUri)).perform();
208                assertSuccess(r);
209                return uriToPath(r.getLocation().toString());
210            } catch (FcrepoOperationFailedException | URISyntaxException e) {
211                throw new RuntimeException(e);
212            }
213        } else if (!exists(path)) {
214            createResource(path);
215        }
216        return path;
217    }
218
219    @Override
220    public String createNonRDFPlaceholder(final String path) {
221        if (!exists(path)) {
222            if (path == null || path.length() == 0) {
223                try {
224                    final FcrepoResponse r = getClient().post(new URI(baseUri))
225                            .body((InputStream) null, "text/plain").perform();
226                    assertSuccess(r);
227                    return uriToPath(r.getLocation().toString());
228                } catch (FcrepoOperationFailedException | URISyntaxException e) {
229                    throw new RuntimeException(e);
230                }
231            } else {
232                try {
233                    assertSuccess(getClient().put(pathToURI(path))
234                            .body(new ByteArrayInputStream("".getBytes("UTF-8")), "text/xml").perform());
235                } catch (FcrepoOperationFailedException | URISyntaxException | UnsupportedEncodingException e) {
236                    throw new RuntimeException(e);
237                }
238                return path;
239            }
240        } else {
241            return path;
242        }
243    }
244
245    @Override
246    public boolean isPlaceholder(final String path) {
247        try {
248            return getClient().get(pathToURI(path + "/fcr:versions")).perform().getStatusCode() == 404;
249        } catch (FcrepoOperationFailedException | URISyntaxException e) {
250            throw new RuntimeException(e);
251        }
252    }
253
254}