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.client.impl; 017 018import static org.apache.http.HttpStatus.SC_CONFLICT; 019import static org.apache.http.HttpStatus.SC_FORBIDDEN; 020import static org.apache.http.HttpStatus.SC_NO_CONTENT; 021import static org.apache.http.HttpStatus.SC_NOT_FOUND; 022import static org.slf4j.LoggerFactory.getLogger; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.text.ParseException; 027import java.text.SimpleDateFormat; 028import java.util.Collection; 029import java.util.Date; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.Set; 033 034import org.apache.http.HttpResponse; 035import org.apache.http.HttpStatus; 036import org.apache.http.StatusLine; 037import org.apache.http.client.methods.HttpDelete; 038import org.apache.http.client.methods.HttpPatch; 039import org.apache.http.client.methods.HttpPost; 040import org.apache.http.client.methods.HttpPut; 041import org.fcrepo.client.FedoraException; 042import org.fcrepo.client.FedoraRepository; 043import org.fcrepo.client.FedoraResource; 044import org.fcrepo.client.ForbiddenException; 045import org.fcrepo.client.NotFoundException; 046import org.fcrepo.client.utils.HttpCopy; 047import org.fcrepo.client.utils.HttpHelper; 048import org.fcrepo.client.utils.HttpMove; 049import org.fcrepo.kernel.api.RdfLexicon; 050import org.slf4j.Logger; 051 052import com.hp.hpl.jena.graph.Graph; 053import com.hp.hpl.jena.graph.Node; 054import com.hp.hpl.jena.graph.NodeFactory; 055import com.hp.hpl.jena.graph.Triple; 056import com.hp.hpl.jena.rdf.model.Property; 057import com.hp.hpl.jena.util.iterator.ExtendedIterator; 058 059/** 060 * A Fedora Object Impl. 061 * 062 * @author lsitu 063 * @author escowles 064 * @since 2014-08-11 065 */ 066public class FedoraResourceImpl implements FedoraResource { 067 private static final Logger LOGGER = getLogger(FedoraResourceImpl.class); 068 069 private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 070 071 protected FedoraRepository repository = null; 072 073 protected HttpHelper httpHelper = null; 074 075 protected String path = null; 076 077 protected String oldPath = null; 078 079 protected Node subject = null; 080 081 protected Graph graph; 082 083 private String etagValue = null; 084 085 /** 086 * FedoraResourceImpl constructor 087 * 088 * @param repository FedoraRepositoryImpl that created this resource 089 * @param httpHelper HTTP helper for making repository requests 090 * @param path Repository path of this resource 091 */ 092 public FedoraResourceImpl(final FedoraRepository repository, final HttpHelper httpHelper, final String path) { 093 this.repository = repository; 094 this.httpHelper = httpHelper; 095 this.path = path; 096 subject = NodeFactory.createURI(repository.getRepositoryUrl() + path); 097 } 098 099 @Override 100 public void copy(final String destination) throws FedoraException { 101 102 final HttpCopy copy = httpHelper.createCopyMethod(path,destination); 103 104 try { 105 final HttpResponse response = httpHelper.execute( copy ); 106 final StatusLine status = response.getStatusLine(); 107 final String uri = copy.getURI().toString(); 108 109 if (status.getStatusCode() == HttpStatus.SC_CREATED) { // Created 110 LOGGER.debug("resource successfully copied from " + path + " to " + destination, uri); 111 } else if (status.getStatusCode() == HttpStatus.SC_CONFLICT) { // Source path doesn't exists 112 LOGGER.error("error copying resource {}: {} {}", uri, status.getStatusCode(), 113 status.getReasonPhrase()); 114 throw new FedoraException("error copying resource " + uri + ": " + status.getStatusCode() + " " + 115 status.getReasonPhrase()); 116 } else if (status.getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { // Destination path already exists 117 LOGGER.error("error copying resource {}: {} {}", uri, status.getStatusCode(), 118 status.getReasonPhrase()); 119 throw new FedoraException("error copying resource " + uri + ": " + status.getStatusCode() + " " + 120 status.getReasonPhrase()); 121 } else if (status.getStatusCode() == HttpStatus.SC_BAD_GATEWAY) { 122 // Destination URI isn't a valid resource path 123 LOGGER.error("error copying resource {}: {} {}", uri, status.getStatusCode(), 124 status.getReasonPhrase()); 125 throw new FedoraException("error copying resource " + uri + ": " + status.getStatusCode() + " " + 126 status.getReasonPhrase()); 127 } 128 } catch (final FedoraException e) { 129 throw e; 130 } catch (final Exception e) { 131 LOGGER.error("could not encode URI parameter", e); 132 throw new FedoraException(e); 133 } finally { 134 copy.releaseConnection(); 135 } 136 } 137 138 @Override 139 public void delete() throws FedoraException { 140 final HttpDelete delete = httpHelper.createDeleteMethod(path); 141 142 try { 143 final HttpResponse response = httpHelper.execute( delete ); 144 final StatusLine status = response.getStatusLine(); 145 final String uri = delete.getURI().toString(); 146 147 if ( status.getStatusCode() == SC_NO_CONTENT) { 148 LOGGER.debug("triples updated successfully for resource {}", uri); 149 } else if ( status.getStatusCode() == SC_NOT_FOUND) { 150 LOGGER.error("resource {} does not exist, cannot update", uri); 151 throw new NotFoundException("resource " + uri + " does not exist, cannot update"); 152 } else { 153 LOGGER.error("error updating resource {}: {} {}", uri, status.getStatusCode(), 154 status.getReasonPhrase()); 155 throw new FedoraException("error updating resource " + uri + ": " + status.getStatusCode() + " " + 156 status.getReasonPhrase()); 157 } 158 } catch (final FedoraException e) { 159 throw e; 160 } catch (final Exception e) { 161 LOGGER.error("Error executing request", e); 162 throw new FedoraException(e); 163 } finally { 164 delete.releaseConnection(); 165 } 166 } 167 168 @Override 169 public void forceDelete() throws FedoraException { 170 delete(); 171 removeTombstone(); 172 } 173 174 /** 175 * Remove tombstone (for the current path) 176 */ 177 public void removeTombstone() throws FedoraException { 178 removeTombstone(path); 179 } 180 181 182 /** 183 * Remove tombstone located at given path 184 */ 185 public void removeTombstone(final String path) throws FedoraException { 186 final HttpDelete delete = httpHelper.createDeleteMethod(path + "/fcr:tombstone"); 187 188 try { 189 final HttpResponse response = httpHelper.execute( delete ); 190 final StatusLine status = response.getStatusLine(); 191 final String uri = delete.getURI().toString(); 192 193 if ( status.getStatusCode() == SC_NO_CONTENT) { 194 LOGGER.debug("triples updated successfully for resource {}", uri); 195 } else if ( status.getStatusCode() == SC_NOT_FOUND) { 196 LOGGER.error("resource {} does not exist, cannot update", uri); 197 throw new NotFoundException("resource " + uri + " does not exist, cannot update"); 198 } else { 199 LOGGER.error("error updating resource {}: {} {}", uri, status.getStatusCode(), 200 status.getReasonPhrase()); 201 throw new FedoraException("error updating resource " + uri + ": " + status.getStatusCode() + " " + 202 status.getReasonPhrase()); 203 } 204 } catch (final FedoraException e) { 205 throw e; 206 } catch (final Exception e) { 207 LOGGER.error("Error executing request", e); 208 throw new FedoraException(e); 209 } finally { 210 delete.releaseConnection(); 211 } 212 } 213 214 @Override 215 public Date getCreatedDate() { 216 return getDate(RdfLexicon.CREATED_DATE); 217 } 218 219 @Override 220 public String getEtagValue() { 221 return etagValue; 222 } 223 224 /** 225 * set etagValue 226 * 227 * @param etagValue string of etagvalue to set 228 */ 229 public void setEtagValue(final String etagValue) { 230 this.etagValue = etagValue; 231 } 232 233 @Override 234 public Date getLastModifiedDate() { 235 return getDate(RdfLexicon.LAST_MODIFIED_DATE); 236 } 237 238 @Override 239 public Collection<String> getMixins() { 240 return getPropertyValues(RdfLexicon.HAS_MIXIN_TYPE); 241 } 242 243 @Override 244 public String getName() { 245 final String p = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; 246 final String[] paths = p.split("/"); 247 return paths[paths.length - 1]; 248 } 249 250 @Override 251 public String getPath() { 252 return path; 253 } 254 255 @Override 256 public Iterator<Triple> getProperties() { 257 return graph.find(Node.ANY, Node.ANY, Node.ANY); 258 } 259 260 @Override 261 public Long getSize() { 262 return (long) graph.size(); 263 } 264 265 @Override 266 public void move(final String destination) throws FedoraException { 267 final HttpMove move = httpHelper.createMoveMethod(path,destination); 268 269 try { 270 final HttpResponse response = httpHelper.execute( move ); 271 final StatusLine status = response.getStatusLine(); 272 final String uri = move.getURI().toString(); 273 274 if (status.getStatusCode() == HttpStatus.SC_CREATED) { // Created 275 LOGGER.debug("resource successfully moved from " + path + " to " + destination, uri); 276 oldPath = path; 277 path = destination; 278 subject = NodeFactory.createURI(repository.getRepositoryUrl() + path); 279 } else if (status.getStatusCode() == HttpStatus.SC_CONFLICT) { // Source path doesn't exists 280 LOGGER.error("error moving resource {}: {} {}", uri, status.getStatusCode(), 281 status.getReasonPhrase()); 282 throw new FedoraException("error moving resource " + uri + ": " + status.getStatusCode() + 283 " " + status.getReasonPhrase()); 284 } else if (status.getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { 285 // Destination path already exists 286 LOGGER.error("error moving resource {}: {} {}", uri, status.getStatusCode(), 287 status.getReasonPhrase()); 288 throw new FedoraException("error moving resource " + uri + ": " + status.getStatusCode() + " " + 289 status.getReasonPhrase()); 290 } else if (status.getStatusCode() == HttpStatus.SC_BAD_GATEWAY) { 291 // Destination URI isn't a valid resource path 292 LOGGER.error("error moving resource {}: {} {}", uri, status.getStatusCode(), 293 status.getReasonPhrase()); 294 throw new FedoraException("error moving resource " + uri + ": " + status.getStatusCode() + " " + 295 status.getReasonPhrase()); 296 } 297 } catch (final FedoraException e) { 298 oldPath = null; 299 throw e; 300 } catch (final Exception e) { 301 LOGGER.error("could not encode URI parameter", e); 302 throw new FedoraException(e); 303 } finally { 304 move.releaseConnection(); 305 } 306 } 307 308 @Override 309 public void forceMove(final String destination) throws FedoraException { 310 move(destination); 311 removeTombstone(oldPath); 312 } 313 314 @Override 315 public void updateProperties(final String sparqlUpdate) throws FedoraException { 316 final HttpPatch patch = httpHelper.createPatchMethod(getPropertiesPath(), sparqlUpdate); 317 318 try { 319 final HttpResponse response = httpHelper.execute( patch ); 320 final StatusLine status = response.getStatusLine(); 321 final String uri = patch.getURI().toString(); 322 323 if ( status.getStatusCode() == SC_NO_CONTENT) { 324 LOGGER.debug("triples updated successfully for resource {}", uri); 325 } else if ( status.getStatusCode() == SC_FORBIDDEN) { 326 LOGGER.error("updating resource {} is not authorized.", uri); 327 throw new ForbiddenException("updating resource " + uri + " is not authorized."); 328 } else if ( status.getStatusCode() == SC_NOT_FOUND) { 329 LOGGER.error("resource {} does not exist, cannot update", uri); 330 throw new NotFoundException("resource " + uri + " does not exist, cannot update"); 331 } else if ( status.getStatusCode() == SC_CONFLICT) { 332 LOGGER.error("resource {} is locked", uri); 333 throw new FedoraException("resource is locked: " + uri); 334 } else { 335 LOGGER.error("error updating resource {}: {} {}", uri, status.getStatusCode(), 336 status.getReasonPhrase()); 337 throw new FedoraException("error updating resource " + uri + ": " + status.getStatusCode() + " " + 338 status.getReasonPhrase()); 339 } 340 341 // update properties from server 342 httpHelper.loadProperties(this); 343 344 } catch (final FedoraException e) { 345 throw e; 346 } catch (final Exception e) { 347 LOGGER.error("could not encode URI parameter", e); 348 throw new FedoraException(e); 349 } finally { 350 patch.releaseConnection(); 351 } 352 } 353 354 @Override 355 public void updateProperties(final InputStream updatedProperties, final String contentType) 356 throws FedoraException { 357 358 final HttpPut put = httpHelper.createTriplesPutMethod(getPropertiesPath(), updatedProperties, contentType); 359 360 try { 361 final HttpResponse response = httpHelper.execute( put ); 362 final StatusLine status = response.getStatusLine(); 363 final String uri = put.getURI().toString(); 364 365 if ( status.getStatusCode() == SC_NO_CONTENT) { 366 LOGGER.debug("triples updated successfully for resource {}", uri); 367 } else if ( status.getStatusCode() == SC_FORBIDDEN) { 368 LOGGER.error("updating resource {} is not authorized.", uri); 369 throw new ForbiddenException("updating resource " + uri + " is not authorized."); 370 } else if ( status.getStatusCode() == SC_NOT_FOUND) { 371 LOGGER.error("resource {} does not exist, cannot update", uri); 372 throw new NotFoundException("resource " + uri + " does not exist, cannot update"); 373 } else if ( status.getStatusCode() == SC_CONFLICT) { 374 LOGGER.error("resource {} is locked", uri); 375 throw new FedoraException("resource is locked: " + uri); 376 } else { 377 LOGGER.error("error updating resource {}: {} {}", uri, status.getStatusCode(), 378 status.getReasonPhrase()); 379 throw new FedoraException("error updating resource " + uri + ": " + status.getStatusCode() + " " + 380 status.getReasonPhrase()); 381 } 382 383 // update properties from server 384 httpHelper.loadProperties(this); 385 386 } catch (final FedoraException e) { 387 throw e; 388 } catch (final Exception e) { 389 LOGGER.error("Error executing request", e); 390 throw new FedoraException(e); 391 } finally { 392 put.releaseConnection(); 393 } 394 } 395 396 @Override 397 public boolean isWritable() { 398 final Collection<String> values = getPropertyValues(RdfLexicon.WRITABLE); 399 if (values != null && values.size() > 0) { 400 final Iterator<String> it = values.iterator(); 401 return Boolean.parseBoolean(it.next()); 402 } 403 return false; 404 } 405 406 @Override 407 public void createVersionSnapshot(final String label) throws FedoraException { 408 final HttpPost postVersion = httpHelper.createPostMethod(path + "/fcr:versions", null); 409 try { 410 postVersion.setHeader("Slug", label); 411 final HttpResponse response = httpHelper.execute(postVersion); 412 final StatusLine status = response.getStatusLine(); 413 final String uri = postVersion.getURI().toString(); 414 415 if ( status.getStatusCode() == SC_NO_CONTENT) { 416 LOGGER.debug("new version created for resource at {}", uri); 417 } else if ( status.getStatusCode() == SC_CONFLICT) { 418 LOGGER.debug("The label {} is in use by another version.", label); 419 throw new FedoraException("The label \"" + label + "\" is in use by another version."); 420 } else if ( status.getStatusCode() == SC_FORBIDDEN) { 421 LOGGER.error("updating resource {} is not authorized.", uri); 422 throw new ForbiddenException("updating resource " + uri + " is not authorized."); 423 } else if ( status.getStatusCode() == SC_NOT_FOUND) { 424 LOGGER.error("resource {} does not exist, cannot create version", uri); 425 throw new NotFoundException("resource " + uri + " does not exist, cannot create version"); 426 } else { 427 LOGGER.error("error updating resource {}: {} {}", uri, status.getStatusCode(), 428 status.getReasonPhrase()); 429 throw new FedoraException("error updating resource " + uri + ": " + status.getStatusCode() + " " + 430 status.getReasonPhrase()); 431 } 432 } catch (IOException e) { 433 LOGGER.error("Error executing request", e); 434 throw new FedoraException(e); 435 } finally { 436 postVersion.releaseConnection(); 437 } 438 } 439 440 /** 441 * Get the properties graph 442 * 443 * @return Graph containing properties for this resource 444 */ 445 public Graph getGraph() { 446 return graph; 447 } 448 449 /** 450 * Update the properties graph 451 * 452 * @param graph graph to add to this object 453 **/ 454 public void setGraph( final Graph graph ) { 455 this.graph = graph; 456 } 457 458 private Date getDate(final Property property) { 459 Date date = null; 460 final Triple t = getTriple(subject, property); 461 if ( t != null ) { 462 final String dateValue = t.getObject().getLiteralValue().toString(); 463 try { 464 date = dateFormat.parse(dateValue); 465 } catch (final ParseException e) { 466 LOGGER.debug("Invalid date format error: " + dateValue); 467 } 468 } 469 return date; 470 } 471 472 /** 473 * Return all the values of a property 474 * 475 * @param property The Property to get values for 476 * @return Collection of values 477 */ 478 protected Collection<String> getPropertyValues(final Property property) { 479 final ExtendedIterator<Triple> iterator = graph.find(Node.ANY, 480 property.asNode(), 481 Node.ANY); 482 final Set<String> set = new HashSet<>(); 483 while (iterator.hasNext()) { 484 final Node object = iterator.next().getObject(); 485 if (object.isLiteral()) { 486 set.add(object.getLiteralValue().toString()); 487 } else if (object.isURI()) { 488 set.add(object.getURI().toString()); 489 } else { 490 set.add(object.toString()); 491 } 492 } 493 return set; 494 } 495 496 protected Triple getTriple( final Node subject, final Property property ) { 497 final ExtendedIterator<Triple> it = graph.find( subject, property.asNode(), null ); 498 try { 499 if ( it.hasNext() ) { 500 return it.next(); 501 } else { 502 return null; 503 } 504 } finally { 505 it.close(); 506 } 507 } 508 509 /** 510 * Gets the path to which properties of this resource may be accessed. 511 * 512 * @return string containing properties path 513 */ 514 public String getPropertiesPath() { 515 return path; 516 } 517 518}