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