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