001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 * created at Oct 18, 2008 021 */ 022package org.biojava.nbio.structure.io; 023 024import org.biojava.nbio.structure.PDBStatus; 025import org.biojava.nbio.structure.PDBStatus.Status; 026import org.biojava.nbio.structure.Structure; 027import org.biojava.nbio.structure.StructureException; 028import org.biojava.nbio.structure.align.util.UserConfiguration; 029import org.biojava.nbio.structure.io.util.FileDownloadUtils; 030import org.biojava.nbio.core.util.InputStreamProvider; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import java.io.File; 035import java.io.IOException; 036import java.io.InputStream; 037import java.net.URL; 038import java.text.ParseException; 039import java.text.SimpleDateFormat; 040import java.util.*; 041 042/** 043 * Superclass for classes which download and interact with the PDB's FTP server, 044 * specifically {@link PDBFileReader} and {@link MMCIFFileReader}. The basic 045 * functionality of downloading structure files from the FTP site is gathered 046 * here, making the child classes responsible for only the specific paths and 047 * file formats needed. 048 * 049 * @author Spencer Bliven 050 * 051 */ 052public abstract class LocalPDBDirectory implements StructureIOFile { 053 054 private static final Logger logger = LoggerFactory.getLogger(LocalPDBDirectory.class); 055 056 /** 057 * The default server name, prefixed by the protocol string (http:// or ftp://). 058 * Note that we don't support file stamp retrieving for ftp protocol, thus some of the 059 * fetch modes will not work properly with ftp protocol 060 */ 061 public static final String DEFAULT_PDB_FILE_SERVER = "http://ftp.wwpdb.org"; 062 public static final String PDB_FILE_SERVER_PROPERTY = "PDB.FILE.SERVER"; 063 064 /** 065 * Behaviors for when an obsolete structure is requested. 066 * @author Spencer Bliven 067 * @see LocalPDBDirectory#setObsoleteBehavior(ObsoleteBehavior) 068 */ 069 public static enum ObsoleteBehavior { 070 /** Fetch the most recent version of the PDB entry. */ 071 FETCH_CURRENT, 072 /** Fetch the obsolete entry from the PDB archives. */ 073 FETCH_OBSOLETE, 074 /** Throw a StructureException for obsolete entries.*/ 075 THROW_EXCEPTION; 076 077 public static final ObsoleteBehavior DEFAULT=THROW_EXCEPTION; 078 } 079 080 /** 081 * Controls when the class should fetch files from the ftp server 082 * @author Spencer Bliven 083 * 084 */ 085 public static enum FetchBehavior { 086 /** Never fetch from the server; Throw errors for missing files */ 087 LOCAL_ONLY, 088 /** Fetch missing files from the server. Don't check for outdated files */ 089 FETCH_FILES, 090 /** 091 * Fetch missing files from the server, also fetch if file present but older than the 092 * server file. 093 * This requires always querying the server for the last modified time of the file, thus 094 * it adds an overhead to getting files from cache. 095 */ 096 FETCH_IF_OUTDATED, 097 /** 098 * Fetch missing files from the server. 099 * Also force the download of files older than {@value #LAST_REMEDIATION_DATE_STRING}. 100 */ 101 FETCH_REMEDIATED, 102 /** For every file, force downloading from the server */ 103 FORCE_DOWNLOAD; 104 105 public static final FetchBehavior DEFAULT = FETCH_REMEDIATED; 106 } 107 108 /** 109 * Date of the latest PDB file remediation 110 */ 111 public static final long LAST_REMEDIATION_DATE ; 112 private static final String LAST_REMEDIATION_DATE_STRING = "2011/07/12"; 113 114 static { 115 116 SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US); 117 118 long t = 0; 119 try { 120 Date d = formatter.parse(LAST_REMEDIATION_DATE_STRING); 121 t = d.getTime(); 122 } catch (ParseException e){ 123 logger.error("Unexpected error! could not parse LAST_REMEDIATION_DATE: "+e.getMessage()); 124 } 125 LAST_REMEDIATION_DATE = t; 126 } 127 128 protected static final String lineSplit = System.getProperty("file.separator"); 129 130 private File path; 131 private List<String> extensions; 132 133 /** 134 * The server name, prefixed by the protocol string (http:// or ftp://). 135 * Note that we don't support file stamp retrieving for ftp protocol, thus some of the 136 * fetch modes will not work properly with ftp protocol 137 */ 138 private String serverName; 139 140 private FileParsingParameters params; 141 142 private ObsoleteBehavior obsoleteBehavior; 143 private FetchBehavior fetchBehavior; 144 145 146 // Cache results of get*DirPath() 147 private String splitDirURL; // path on the server, starting with a slash and ending before the 2-char split directories 148 private String obsoleteDirURL; 149 private File splitDirPath; // path to the directory before the 2-char split 150 private File obsoleteDirPath; 151 152 /** 153 * Subclasses should provide default and single-string constructors. 154 * They should use {@link #addExtension(String)} to add one or more extensions. 155 * 156 * <p>If path is null, initialize using the system property/environment variable 157 * {@link UserConfiguration#PDB_DIR}. 158 * @param path Path to the PDB file directory 159 */ 160 public LocalPDBDirectory(String path) { 161 extensions = new ArrayList<String>(); 162 163 params = new FileParsingParameters(); 164 165 if( path == null) { 166 UserConfiguration config = new UserConfiguration(); 167 path = config.getPdbFilePath(); 168 logger.debug("Initialising from system property/environment variable to path: {}", path.toString()); 169 } else { 170 path = FileDownloadUtils.expandUserHome(path); 171 logger.debug("Initialising with path {}", path.toString()); 172 } 173 this.path = new File(path); 174 175 this.serverName = getServerName(); 176 177 // Initialize splitDirURL,obsoleteDirURL,splitDirPath,obsoleteDirPath 178 initPaths(); 179 180 fetchBehavior = FetchBehavior.DEFAULT; 181 obsoleteBehavior = ObsoleteBehavior.DEFAULT; 182 } 183 184 public LocalPDBDirectory() { 185 this(null); 186 } 187 188 /** 189 * Sets the path for the directory where PDB files are read/written 190 */ 191 public void setPath(String p){ 192 path = new File(FileDownloadUtils.expandUserHome(p)) ; 193 initPaths(); 194 } 195 196 /** 197 * Returns the path value. 198 * @return a String representing the path value 199 * @see #setPath 200 * 201 */ 202 public String getPath() { 203 return path.toString() ; 204 } 205 206 /** define supported file extensions 207 * compressed extensions .Z,.gz do not need to be specified 208 * they are dealt with automatically. 209 */ 210 @Override 211 public void addExtension(String s){ 212 //System.out.println("add Extension "+s); 213 extensions.add(s); 214 } 215 216 @Override 217 public List<String> getExtensions() { 218 return Collections.unmodifiableList(extensions); 219 } 220 221 /** clear the supported file extensions 222 * 223 */ 224 public void clearExtensions(){ 225 extensions.clear(); 226 } 227 228 /** 229 * @deprecated Use {@link #getFetchBehavior()} 230 */ 231 @Deprecated 232 public boolean isAutoFetch() { 233 return fetchBehavior != FetchBehavior.LOCAL_ONLY; 234 } 235 236 /** 237 * @deprecated Use {@link #setFetchBehavior()} 238 */ 239 @Deprecated 240 public void setAutoFetch(boolean autoFetch) { 241 logger.warn("LocalPDBDirectory.setAutoFetch() is deprecated, please use LocalPDBDirectory.setFetchBehavior() instead. The option will be removed in upcoming releases"); 242 if(autoFetch) { 243 setFetchBehavior(FetchBehavior.DEFAULT); 244 } else { 245 setFetchBehavior(FetchBehavior.LOCAL_ONLY); 246 } 247 } 248 249 @Override 250 public void setFileParsingParameters(FileParsingParameters params){ 251 this.params= params; 252 } 253 254 @Override 255 public FileParsingParameters getFileParsingParameters(){ 256 return params; 257 } 258 259 /** 260 * <b>[Optional]</b> This method changes the behavior when obsolete entries 261 * are requested. Current behaviors are: 262 * <ul> 263 * <li>{@link ObsoleteBehavior#THROW_EXCEPTION THROW_EXCEPTION} 264 * Throw a {@link StructureException} (the default) 265 * <li>{@link ObsoleteBehavior#FETCH_OBSOLETE FETCH_OBSOLETE} 266 * Load the requested ID from the PDB's obsolete repository 267 * <li>{@link ObsoleteBehavior#FETCH_CURRENT FETCH_CURRENT} 268 * Load the most recent version of the requested structure 269 * 270 * <p>This setting may be silently ignored by implementations which do not have 271 * access to the server to determine whether an entry is obsolete, such as 272 * if {@link #isAutoFetch()} is false. Note that an obsolete entry may still be 273 * returned even this is FETCH_CURRENT if the entry is found locally. 274 * 275 * @param fetchFileEvenIfObsolete Whether to fetch obsolete records 276 * @see #setFetchCurrent(boolean) 277 * @since 4.0.0 278 */ 279 public void setObsoleteBehavior(ObsoleteBehavior behavior) { 280 obsoleteBehavior = behavior; 281 } 282 283 /** 284 * Returns how this instance deals with obsolete entries. Note that this 285 * setting may be ignored by some implementations or in some situations, 286 * such as when {@link #isAutoFetch()} is false. 287 * 288 * <p>For most implementations, the default value is 289 * {@link ObsoleteBehavior#THROW_EXCEPTION THROW_EXCEPTION}. 290 * 291 * @return The ObsoleteBehavior 292 * @since 4.0.0 293 */ 294 public ObsoleteBehavior getObsoleteBehavior() { 295 return obsoleteBehavior; 296 } 297 298 /** 299 * Get the behavior for fetching files from the server 300 * @return 301 */ 302 public FetchBehavior getFetchBehavior() { 303 return fetchBehavior; 304 } 305 /** 306 * Set the behavior for fetching files from the server. 307 * This replaces the {@link #setAutoFetch(boolean)} method with a more 308 * extensive set of options. 309 * @param fetchBehavior 310 */ 311 public void setFetchBehavior(FetchBehavior fetchBehavior) { 312 this.fetchBehavior = fetchBehavior; 313 } 314 315 316 @Override 317 public Structure getStructure(String filename) throws IOException 318 { 319 filename = FileDownloadUtils.expandUserHome(filename); 320 File f = new File(filename); 321 return getStructure(f); 322 323 } 324 325 public Structure getStructure(URL u) throws IOException{ 326 InputStreamProvider isp = new InputStreamProvider(); 327 InputStream inStream = isp.getInputStream(u); 328 return getStructure(inStream); 329 } 330 331 @Override 332 public Structure getStructure(File filename) throws IOException { 333 InputStreamProvider isp = new InputStreamProvider(); 334 335 InputStream inStream = isp.getInputStream(filename); 336 337 return getStructure(inStream); 338 } 339 340 341 @Override 342 public Structure getStructureById(String pdbId) throws IOException { 343 InputStream inStream = getInputStream(pdbId); 344 345 return getStructure(inStream); 346 } 347 348 /** 349 * Handles the actual parsing of the file into a Structure object. 350 * @param inStream 351 * @return 352 * @throws IOException 353 */ 354 public abstract Structure getStructure(InputStream inStream) throws IOException; 355 356 /** 357 * Load or download the specified structure and return it as an InputStream 358 * for direct parsing. 359 * @param pdbId 360 * @return 361 * @throws IOException 362 */ 363 protected InputStream getInputStream(String pdbId) throws IOException{ 364 365 if ( pdbId.length() != 4) 366 throw new IOException("The provided ID does not look like a PDB ID : " + pdbId); 367 368 // Check existing 369 File file = downloadStructure(pdbId); 370 371 if(!file.exists()) { 372 throw new IOException("Structure "+pdbId+" not found and unable to download."); 373 } 374 375 InputStreamProvider isp = new InputStreamProvider(); 376 377 InputStream inputStream = isp.getInputStream(file); 378 379 return inputStream; 380 } 381 382 /** 383 * Download a structure, but don't parse it yet or store it in memory. 384 * 385 * Used to pre-fetch large numbers of structures. 386 * @param pdbId 387 * @throws IOException 388 */ 389 public void prefetchStructure(String pdbId) throws IOException { 390 if ( pdbId.length() != 4) 391 throw new IOException("The provided ID does not look like a PDB ID : " + pdbId); 392 393 // Check existing 394 File file = downloadStructure(pdbId); 395 396 if(!file.exists()) { 397 throw new IOException("Structure "+pdbId+" not found and unable to download."); 398 } 399 } 400 401 /** 402 * Attempts to delete all versions of a structure from the local directory. 403 * @param pdbId 404 * @return True if one or more files were deleted 405 */ 406 public boolean deleteStructure(String pdbId){ 407 boolean deleted = false; 408 // Force getLocalFile to check in obsolete locations 409 ObsoleteBehavior obsolete = getObsoleteBehavior(); 410 setObsoleteBehavior(ObsoleteBehavior.FETCH_OBSOLETE); 411 412 try { 413 File existing = getLocalFile(pdbId); 414 while(existing != null) { 415 assert(existing.exists()); // should exist unless concurrency problems 416 417 if( getFetchBehavior() == FetchBehavior.LOCAL_ONLY) { 418 throw new RuntimeException("Refusing to delete from LOCAL_ONLY directory"); 419 } 420 421 // delete file 422 boolean success = existing.delete(); 423 if(success) { 424 logger.info("Deleting "+existing.getAbsolutePath()); 425 } 426 deleted = deleted || success; 427 428 // delete parent if empty 429 File parent = existing.getParentFile(); 430 if(parent != null) { 431 success = parent.delete(); 432 if(success) { 433 logger.info("Deleting "+parent.getAbsolutePath()); 434 } 435 } 436 437 existing = getLocalFile(pdbId); 438 } 439 return deleted; 440 441 } finally { 442 setObsoleteBehavior(obsolete); 443 } 444 } 445 446 /** 447 * Downloads an MMCIF file from the PDB to the local path 448 * @param pdbId 449 * @return The file, or null if it was unavailable for download 450 * @throws IOException for errors downloading or writing, or if the 451 * fetchBehavior is {@link FetchBehavior#LOCAL_ONLY} 452 */ 453 @SuppressWarnings("deprecation") //for isUpdateRemediatedFiles() 454 protected File downloadStructure(String pdbId) throws IOException{ 455 if ( pdbId.length() != 4) 456 throw new IOException("The provided ID does not look like a PDB ID : " + pdbId); 457 458 // decide whether download is required 459 File existing = getLocalFile(pdbId); 460 switch(fetchBehavior) { 461 case LOCAL_ONLY: 462 if( existing == null ) { 463 throw new IOException(String.format("Structure %s not found in %s " 464 + "and configured not to download.",pdbId,getPath())); 465 } else { 466 return existing; 467 } 468 case FETCH_FILES: 469 // Use existing if present 470 if( existing != null) { 471 // Respect deprecated remediation parameter for backwards compatibility 472 if(getFileParsingParameters().isUpdateRemediatedFiles()) { 473 long lastModified = existing.lastModified(); 474 475 if (lastModified < LAST_REMEDIATION_DATE) { 476 logger.warn("FileParsingParameters.setUpdateRemediatedFiles() is deprecated, please use LocalPDBDirectory.setFetchBehavior() instead. The option will be removed in upcoming releases"); 477 // the file is too old, replace with newer version 478 logger.warn("Replacing file {} with latest remediated (remediation of {}) file from PDB.", 479 existing, LAST_REMEDIATION_DATE_STRING); 480 break; 481 } 482 } 483 484 return existing; 485 } 486 // existing is null, downloadStructure(String,String,boolean,File) will download it 487 break; 488 case FETCH_IF_OUTDATED: 489 // here existing can be null or not: 490 // existing == null : downloadStructure(String,String,boolean,File) will download it 491 // existing != null : downloadStructure(String,String,boolean,File) will check its date and download if older 492 break; 493 case FETCH_REMEDIATED: 494 // Use existing if present and recent enough 495 if( existing != null) { 496 long lastModified = existing.lastModified(); 497 498 if (lastModified < LAST_REMEDIATION_DATE) { 499 // the file is too old, replace with newer version 500 logger.warn("Replacing file {} with latest remediated (remediation of {}) file from PDB.", 501 existing, LAST_REMEDIATION_DATE_STRING); 502 existing = null; 503 break; 504 } else { 505 return existing; 506 } 507 } 508 case FORCE_DOWNLOAD: 509 // discard the existing file to force redownload 510 existing = null; // downloadStructure(String,String,boolean,File) will download it 511 break; 512 } 513 514 // Force the download now 515 if(obsoleteBehavior == ObsoleteBehavior.FETCH_CURRENT) { 516 String current = PDBStatus.getCurrent(pdbId); 517 518 if(current == null) { 519 // either an error or there is not current entry 520 current = pdbId; 521 } 522 return downloadStructure(current, splitDirURL,false, existing); 523 } else if(obsoleteBehavior == ObsoleteBehavior.FETCH_OBSOLETE 524 && PDBStatus.getStatus(pdbId) == Status.OBSOLETE) { 525 return downloadStructure(pdbId, obsoleteDirURL, true, existing); 526 } else { 527 return downloadStructure(pdbId, splitDirURL, false, existing); 528 } 529 } 530 531 /** 532 * Download a file from the ftp server, replacing any existing files if needed 533 * @param pdbId PDB ID 534 * @param pathOnServer Path on the FTP server, e.g. data/structures/divided/pdb 535 * @param obsolete Whether or not file should be saved to the obsolete location locally 536 * @param existingFile if not null and checkServerFileDate is true, the last modified date of the 537 * server file and this file will be compared to decide whether to download or not 538 * @return 539 * @throws IOException 540 */ 541 private File downloadStructure(String pdbId, String pathOnServer, boolean obsolete, File existingFile) 542 throws IOException{ 543 544 File dir = getDir(pdbId,obsolete); 545 File realFile = new File(dir,getFilename(pdbId)); 546 547 String ftp = String.format("%s%s/%s/%s", 548 serverName, pathOnServer, pdbId.substring(1,3).toLowerCase(), getFilename(pdbId)); 549 550 URL url = new URL(ftp); 551 552 Date serverFileDate = null; 553 if (existingFile!=null) { 554 555 serverFileDate = getLastModifiedTime(url); 556 557 if (serverFileDate!=null) { 558 if (existingFile.lastModified()>=serverFileDate.getTime()) { 559 return existingFile; 560 } else { 561 // otherwise we go ahead and download, warning about it first 562 logger.warn("File {} is outdated, will download new one from PDB (updated on {})", 563 existingFile, serverFileDate.toString()); 564 } 565 } else { 566 logger.warn("Could not determine if file {} is outdated (could not get timestamp from server). Will force redownload", existingFile); 567 } 568 } 569 570 logger.info("Fetching " + ftp); 571 logger.info("Writing to "+ realFile); 572 573 574 575 FileDownloadUtils.downloadFile(url, realFile); 576 577 // Commented out following code used for setting the modified date to the downloaded file - JD 2015-01-15 578 // The only reason to have it was in order to get an rsync-like behavior, respecting the timestamps 579 // but the issue is that it would make the FETCH_REMEDIATED mode redownload files with timestamps before 580 // the remediation. 581 //if (serverFileDate==null) 582 // serverFileDate = getLastModifiedTime(url); 583 // 584 //if (serverFileDate!=null) { 585 // logger.debug("Setting modified time of downloaded file {} to {}",realFile,serverFileDate.toString()); 586 // realFile.setLastModified(serverFileDate.getTime()); 587 //} else { 588 // logger.warn("Unknown modified time of file {}, will set its modified time to now.", ftp); 589 //} 590 591 592 return realFile; 593 } 594 595 /** 596 * Get the last modified time of the file in given url by retrieveing the "Last-Modified" header. 597 * Note that this only works for http URLs 598 * @param url 599 * @return the last modified date or null if it couldn't be retrieved (in that case a warning will be logged) 600 */ 601 private Date getLastModifiedTime(URL url) { 602 603 // see http://stackoverflow.com/questions/2416872/how-do-you-obtain-modified-date-from-a-remote-file-java 604 Date date = null; 605 try { 606 String lastModified = url.openConnection().getHeaderField("Last-Modified"); 607 logger.debug("Last modified date of server file ({}) is {}",url.toString(),lastModified); 608 609 610 if (lastModified!=null) { 611 612 try { 613 date = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH).parse(lastModified); 614 } catch (ParseException e) { 615 logger.warn("Could not parse last modified time from string '{}', no last modified time available for file {}", 616 lastModified, url.toString()); 617 // this will return null 618 } 619 620 } 621 } catch (IOException e) { 622 logger.warn("Problems while retrieving last modified time for file {}", url.toString()); 623 } 624 return date; 625 626 } 627 628 /** 629 * Gets the directory in which the file for a given MMCIF file would live, 630 * creating it if necessary. 631 * 632 * The obsolete parameter is necessary to avoid additional server queries. 633 * @param pdbId 634 * @param obsolete Whether the pdbId is obsolete or not 635 * @return File pointing to the directory, 636 */ 637 protected File getDir(String pdbId, boolean obsolete) { 638 639 File dir = null; 640 641 if (obsolete) { 642 // obsolete is always split 643 String middle = pdbId.substring(1,3).toLowerCase(); 644 dir = new File(obsoleteDirPath, middle); 645 } else { 646 String middle = pdbId.substring(1,3).toLowerCase(); 647 dir = new File(splitDirPath, middle); 648 } 649 650 651 if (!dir.exists()) { 652 boolean success = dir.mkdirs(); 653 if (!success) logger.error("Could not create mmCIF dir {}",dir.toString()); 654 } 655 656 return dir; 657 } 658 659 /** 660 * Searches for previously downloaded files 661 * @param pdbId 662 * @return A file pointing to the existing file, or null if not found 663 */ 664 public File getLocalFile(String pdbId) { 665 666 // Search for existing files 667 668 // Search directories: 669 // 1) LOCAL_MMCIF_SPLIT_DIR/<middle>/(pdb)?<pdbId>.<ext> 670 // 2) LOCAL_MMCIF_ALL_DIR/<middle>/(pdb)?<pdbId>.<ext> 671 LinkedList<File> searchdirs = new LinkedList<File>(); 672 String middle = pdbId.substring(1,3).toLowerCase(); 673 674 File splitdir = new File(splitDirPath, middle); 675 searchdirs.add(splitdir); 676 // Search obsolete files if requested 677 if(getObsoleteBehavior() == ObsoleteBehavior.FETCH_OBSOLETE) { 678 File obsdir = new File(obsoleteDirPath,middle); 679 searchdirs.add(obsdir); 680 } 681 682 // valid prefixes before the <pdbId> in the filename 683 String[] prefixes = new String[] {"", "pdb"}; 684 685 for( File searchdir :searchdirs){ 686 for( String prefix : prefixes) { 687 for(String ex : getExtensions() ){ 688 File f = new File(searchdir,prefix + pdbId.toLowerCase() + ex) ; 689 if ( f.exists()) { 690 return f; 691 } 692 } 693 } 694 } 695 //Not found 696 return null; 697 } 698 699 protected boolean checkFileExists(String pdbId){ 700 File path = getLocalFile(pdbId); 701 if ( path != null) 702 return true; 703 return false; 704 } 705 706 /** 707 * Return the String with the PDB server name, including the leading protocol 708 * String (http:// or ftp://). 709 * The server name will be by default the value {@value #DEFAULT_PDB_FILE_SERVER} or the one 710 * read from system property {@value #PDB_FILE_SERVER_PROPERTY} 711 * 712 * @return the server name including the leading protocol string 713 */ 714 public static String getServerName() { 715 String name = System.getProperty(PDB_FILE_SERVER_PROPERTY); 716 717 if ( name == null || name.trim().isEmpty()) { 718 name = DEFAULT_PDB_FILE_SERVER; 719 logger.debug("Using default PDB file server {}",name); 720 } else { 721 if (!name.startsWith("http://") && !name.startsWith("ftp://")) { 722 logger.warn("Server name {} read from system property {} does not have a leading protocol string. Adding http:// to it", name, PDB_FILE_SERVER_PROPERTY); 723 name = "http://"+name; 724 } 725 logger.info("Using PDB file server {} read from system property {}", name, PDB_FILE_SERVER_PROPERTY); 726 } 727 return name; 728 } 729 730 /** 731 * Should be called whenever any of the path variables change. 732 * Thus, if {@link getSplitDirPath()} or {@link getObsoleteDirPath()} 733 * depend on anything, they should call this function when that thing 734 * changes (possibly including at the end of the constructor). 735 */ 736 protected void initPaths() { 737 // Hand-rolled String.join(), for java 6 738 String[] split = getSplitDirPath(); 739 String[] obsolete = getObsoleteDirPath(); 740 741 //URLs are joined with '/' 742 StringBuilder splitURL = new StringBuilder("/pub/pdb"); 743 for(int i=0;i<split.length;i++) { 744 splitURL.append("/"); 745 splitURL.append(split[i]); 746 } 747 StringBuilder obsoleteURL = new StringBuilder("/pub/pdb"); 748 for(int i=0;i<obsolete.length;i++) { 749 obsoleteURL.append("/"); 750 obsoleteURL.append(obsolete[i]); 751 } 752 753 splitDirURL = splitURL.toString(); 754 obsoleteDirURL = obsoleteURL.toString(); 755 756 757 //Files join themselves iteratively 758 splitDirPath = path; 759 for(int i=0;i<split.length;i++) { 760 splitDirPath = new File(splitDirPath,split[i]); 761 } 762 obsoleteDirPath = path; 763 for(int i=0;i<obsolete.length;i++) { 764 obsoleteDirPath = new File(obsoleteDirPath,obsolete[i]); 765 } 766 } 767 768 /** 769 * Converts a PDB ID into a filename with the proper extension 770 * @param pdbId 771 * @return The filename, e.g. "4hhb.pdb.gz" 772 */ 773 protected abstract String getFilename(String pdbId); 774 775 /** 776 * Location of split files within the directory, as an array of paths. 777 * These will be joined with either slashes (for the URL) or the file 778 * separator (for directories). The returned results should be constant, 779 * to allow for caching. 780 * @return A list of directories, relative to the /pub/pdb directory on the server 781 */ 782 protected abstract String[] getSplitDirPath(); 783 /** 784 * Location of obsolete files within the directory, as an array of paths. 785 * These will be joined with either slashes (for the URL) or the file 786 * separator (for directories). The returned results should be constant, 787 * to allow for caching. 788 * @return A list of directories, relative to the /pub/pdb directory on the server 789 */ 790 protected abstract String[] getObsoleteDirPath(); 791}