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}