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://files.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 /** Minimum size for a valid structure file (CIF or PDB), in bytes */ 142 public static final long MIN_PDB_FILE_SIZE = 40; // Empty gzip files are 20bytes. Add a few more for buffer. 143 144 private File path; 145 private List<String> extensions; 146 147 /** 148 * The server name, prefixed by the protocol string (http:// or ftp://). 149 * Note that we don't support file stamp retrieving for ftp protocol, thus some of the 150 * fetch modes will not work properly with ftp protocol 151 */ 152 private String serverName; 153 154 private FileParsingParameters params; 155 156 private ObsoleteBehavior obsoleteBehavior; 157 private FetchBehavior fetchBehavior; 158 159 160 // Cache results of get*DirPath() 161 private String splitDirURL; // path on the server, starting with a slash and ending before the 2-char split directories 162 private String obsoleteDirURL; 163 private File splitDirPath; // path to the directory before the 2-char split 164 private File obsoleteDirPath; 165 166 /** 167 * Subclasses should provide default and single-string constructors. 168 * They should use {@link #addExtension(String)} to add one or more extensions. 169 * 170 * <p>If path is null, initialize using the system property/environment variable 171 * {@link UserConfiguration#PDB_DIR}. 172 * @param path Path to the PDB file directory 173 */ 174 public LocalPDBDirectory(String path) { 175 extensions = new ArrayList<>(); 176 177 params = new FileParsingParameters(); 178 179 if( path == null) { 180 UserConfiguration config = new UserConfiguration(); 181 path = config.getPdbFilePath(); 182 logger.debug("Initialising from system property/environment variable to path: {}", path); 183 } else { 184 path = FileDownloadUtils.expandUserHome(path); 185 logger.debug("Initialising with path {}", path); 186 } 187 this.path = new File(path); 188 189 this.serverName = getServerName(); 190 191 // Initialize splitDirURL,obsoleteDirURL,splitDirPath,obsoleteDirPath 192 initPaths(); 193 194 fetchBehavior = FetchBehavior.DEFAULT; 195 obsoleteBehavior = ObsoleteBehavior.DEFAULT; 196 } 197 198 public LocalPDBDirectory() { 199 this(null); 200 } 201 202 /** 203 * Sets the path for the directory where PDB files are read/written 204 */ 205 public void setPath(String p){ 206 path = new File(FileDownloadUtils.expandUserHome(p)) ; 207 initPaths(); 208 } 209 210 /** 211 * Returns the path value. 212 * @return a String representing the path value 213 * @see #setPath 214 * 215 */ 216 public String getPath() { 217 return path.toString() ; 218 } 219 220 /** define supported file extensions 221 * compressed extensions .Z,.gz do not need to be specified 222 * they are dealt with automatically. 223 */ 224 @Override 225 public void addExtension(String s){ 226 //System.out.println("add Extension "+s); 227 extensions.add(s); 228 } 229 230 @Override 231 public List<String> getExtensions() { 232 return Collections.unmodifiableList(extensions); 233 } 234 235 /** clear the supported file extensions 236 * 237 */ 238 public void clearExtensions(){ 239 extensions.clear(); 240 } 241 242 @Override 243 public void setFileParsingParameters(FileParsingParameters params){ 244 this.params= params; 245 } 246 247 @Override 248 public FileParsingParameters getFileParsingParameters(){ 249 return params; 250 } 251 252 /** 253 * <b>[Optional]</b> This method changes the behavior when obsolete entries 254 * are requested. Current behaviors are: 255 * <ul> 256 * <li>{@link ObsoleteBehavior#THROW_EXCEPTION THROW_EXCEPTION} 257 * Throw a {@link StructureException} (the default) 258 * <li>{@link ObsoleteBehavior#FETCH_OBSOLETE FETCH_OBSOLETE} 259 * Load the requested ID from the PDB's obsolete repository 260 * <li>{@link ObsoleteBehavior#FETCH_CURRENT FETCH_CURRENT} 261 * Load the most recent version of the requested structure 262 * </ul> 263 * <p>This setting may be silently ignored by implementations which do not have 264 * access to the server to determine whether an entry is obsolete, such as 265 * certain {@link FetchBehavior}s. Note that an obsolete entry may still be 266 * returned even this is FETCH_CURRENT if the entry is found locally. 267 * 268 * @param behavior Whether to fetch obsolete records 269 * @see #setFetchBehavior(FetchBehavior) 270 * @since 4.0.0 271 */ 272 public void setObsoleteBehavior(ObsoleteBehavior behavior) { 273 obsoleteBehavior = behavior; 274 } 275 276 /** 277 * Returns how this instance deals with obsolete entries. Note that this 278 * setting may be ignored by some implementations or in some situations, 279 * such as certain {@link FetchBehavior}s. 280 * 281 * <p>For most implementations, the default value is 282 * {@link ObsoleteBehavior#THROW_EXCEPTION THROW_EXCEPTION}. 283 * 284 * @return The ObsoleteBehavior 285 * @since 4.0.0 286 */ 287 public ObsoleteBehavior getObsoleteBehavior() { 288 return obsoleteBehavior; 289 } 290 291 /** 292 * Get the behavior for fetching files from the server 293 * @return 294 */ 295 public FetchBehavior getFetchBehavior() { 296 return fetchBehavior; 297 } 298 299 /** 300 * Set the behavior for fetching files from the server. 301 * @param fetchBehavior 302 */ 303 public void setFetchBehavior(FetchBehavior fetchBehavior) { 304 this.fetchBehavior = fetchBehavior; 305 } 306 307 308 @Override 309 public Structure getStructure(String filename) throws IOException 310 { 311 filename = FileDownloadUtils.expandUserHome(filename); 312 File f = new File(filename); 313 return getStructure(f); 314 315 } 316 317 public Structure getStructure(URL u) throws IOException{ 318 InputStreamProvider isp = new InputStreamProvider(); 319 InputStream inStream = isp.getInputStream(u); 320 return getStructure(inStream); 321 } 322 323 @Override 324 public Structure getStructure(File filename) throws IOException { 325 InputStreamProvider isp = new InputStreamProvider(); 326 327 InputStream inStream = isp.getInputStream(filename); 328 329 return getStructure(inStream); 330 } 331 332 333 /** 334 *{@inheritDoc} 335 */ 336 public Structure getStructureById(String pdbId) throws IOException { 337 return getStructureById(new PdbId(pdbId)); 338 } 339 340 /** 341 *{@inheritDoc} 342 */ 343 @Override 344 public Structure getStructureById(PdbId pdbId) throws IOException { 345 InputStream inStream = getInputStream(pdbId); 346 return getStructure(inStream); 347 } 348 349 /** 350 * Handles the actual parsing of the file into a Structure object. 351 * @param inStream 352 * @return 353 * @throws IOException 354 */ 355 public abstract Structure getStructure(InputStream inStream) throws IOException; 356 357 /** 358 * Load or download the specified structure and return it as an InputStream 359 * for direct parsing. 360 * @param pdbId 361 * @return 362 * @throws IOException in cases of file I/O, including failure to download a healthy (non-corrupted) file. 363 */ 364 protected InputStream getInputStream(PdbId pdbId) throws IOException{ 365 366 // Check existing 367 File file = downloadStructure(pdbId); 368 369 if(!file.exists()) { 370 throw new IOException("Structure "+pdbId+" not found and unable to download."); 371 } 372 373 if(! FileDownloadUtils.validateFile(file)) 374 throw new IOException("Downloaded file invalid: "+file); 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 in cases of file I/O, including failure to download a healthy (non-corrupted) file. 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 if(! FileDownloadUtils.validateFile(file)) 399 throw new IOException("Downloaded file invalid: "+file); 400 } 401 402 /** 403 * Attempts to delete all versions of a structure from the local directory. 404 * @param pdbId a String representing the PDB ID. 405 * @return True if one or more files were deleted 406 * @throws IOException if the file cannot be deleted 407 */ 408 public boolean deleteStructure(String pdbId) throws IOException { 409 return deleteStructure(new PdbId(pdbId)); 410 } 411 412 /** 413 * Attempts to delete all versions of a structure from the local directory. 414 * @param pdbId The PDB ID 415 * @return True if one or more files were deleted 416 * @throws IOException if the file cannot be deleted 417 */ 418 public boolean deleteStructure(PdbId pdbId) throws IOException{ 419 boolean deleted = false; 420 // Force getLocalFile to check in obsolete locations 421 ObsoleteBehavior obsolete = getObsoleteBehavior(); 422 setObsoleteBehavior(ObsoleteBehavior.FETCH_OBSOLETE); 423 424 try { 425 File existing = getLocalFile(pdbId); 426 while(existing != null) { 427 assert(existing.exists()); // should exist unless concurrency problems 428 429 if( getFetchBehavior() == FetchBehavior.LOCAL_ONLY) { 430 throw new RuntimeException("Refusing to delete from LOCAL_ONLY directory"); 431 } 432 433 // delete file 434 boolean success = existing.delete(); 435 if(success) { 436 logger.debug("Deleting {}", existing.getAbsolutePath()); 437 } 438 deleted = deleted || success; 439 440 // delete parent if empty 441 File parent = existing.getParentFile(); 442 if(parent != null) { 443 success = parent.delete(); 444 if(success) { 445 logger.debug("Deleting {}", parent.getAbsolutePath()); 446 } 447 } 448 449 existing = getLocalFile(pdbId); 450 } 451 return deleted; 452 453 } finally { 454 setObsoleteBehavior(obsolete); 455 } 456 } 457 458 /** 459 * Downloads an MMCIF file from the PDB to the local path 460 * @param pdbId 461 * @return The file, or null if it was unavailable for download 462 * @throws IOException for errors downloading or writing, or if the 463 * fetchBehavior is {@link FetchBehavior#LOCAL_ONLY} 464 */ 465 protected File downloadStructure(PdbId pdbId) throws IOException { 466 467 // decide whether download is required 468 File existing = getLocalFile(pdbId); 469 switch(fetchBehavior) { 470 case LOCAL_ONLY: 471 if( existing == null ) { 472 throw new IOException(String.format("Structure %s not found in %s " 473 + "and configured not to download.",pdbId,getPath())); 474 } else { 475 return existing; 476 } 477 case FETCH_FILES: 478 // Use existing if present 479 if( existing != null) { 480 return existing; 481 } 482 // existing is null, downloadStructure(String,String,boolean,File) will download it 483 break; 484 case FETCH_IF_OUTDATED: 485 // here existing can be null or not: 486 // existing == null : downloadStructure(String,String,boolean,File) will download it 487 // existing != null : downloadStructure(String,String,boolean,File) will check its date and download if older 488 break; 489 case FETCH_REMEDIATED: 490 // Use existing if present and recent enough 491 if( existing != null) { 492 long lastModified = existing.lastModified(); 493 494 if (lastModified < LAST_REMEDIATION_DATE) { 495 // the file is too old, replace with newer version 496 logger.warn("Replacing file {} with latest remediated (remediation of {}) file from PDB.", 497 existing, LAST_REMEDIATION_DATE_STRING); 498 existing = null; 499 break; 500 } else { 501 return existing; 502 } 503 } 504 case FORCE_DOWNLOAD: 505 // discard the existing file to force redownload 506 existing = null; // downloadStructure(String,String,boolean,File) will download it 507 break; 508 } 509 510 // Force the download now 511 if(obsoleteBehavior == ObsoleteBehavior.FETCH_CURRENT) { 512 String current = PDBStatus.getCurrent(pdbId.getId()); 513 PdbId pdbIdToDownload = null; 514 if(current == null) { 515 // either an error or there is not current entry 516 pdbIdToDownload = pdbId; 517 }else { 518 pdbIdToDownload = new PdbId(current); 519 } 520 return downloadStructure(pdbIdToDownload, splitDirURL,false, existing); 521 } else if(obsoleteBehavior == ObsoleteBehavior.FETCH_OBSOLETE 522 && PDBStatus.getStatus(pdbId.getId()) == Status.REMOVED) { 523 return downloadStructure(pdbId, obsoleteDirURL, true, existing); 524 } else { 525 return downloadStructure(pdbId, splitDirURL, false, existing); 526 } 527 } 528 529 /** 530 * Download a file from the http server +/- its validation metadata, replacing any existing files if needed 531 * @param pdbId PDB ID 532 * @param pathOnServer Path on the http server, e.g. data/structures/divided/pdb 533 * @param obsolete Whether or not file should be saved to the obsolete location locally 534 * @param existingFile if not null and checkServerFileDate is true, the last modified date of the 535 * server file and this file will be compared to decide whether to download or not 536 * @return 537 * @throws IOException in cases of file I/O, including failure to download a healthy (non-corrupted) file. 538 */ 539 private File downloadStructure(PdbId pdbId, String pathOnServer, boolean obsolete, File existingFile) 540 throws IOException{ 541 String id = pdbId.getId().toLowerCase(); 542 File dir = getDir(id, obsolete); 543 File realFile = new File(dir,getFilename(id)); 544 545 String ftp; 546 547 String filename = getFilename(id); 548 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.createValidationFiles(url, realFile, null, FileDownloadUtils.Hash.UNKNOWN); 580 FileDownloadUtils.downloadFile(url, realFile); 581 if(! FileDownloadUtils.validateFile(realFile)) 582 throw new IOException("Downloaded file invalid: "+realFile); 583 584 return realFile; 585 } 586 587 /** 588 * Get the last modified time of the file in given url by retrieveing the "Last-Modified" header. 589 * Note that this only works for http URLs 590 * @param url 591 * @return the last modified date or null if it couldn't be retrieved (in that case a warning will be logged) 592 */ 593 private Date getLastModifiedTime(URL url) { 594 595 // see http://stackoverflow.com/questions/2416872/how-do-you-obtain-modified-date-from-a-remote-file-java 596 Date date = null; 597 try { 598 String lastModified = url.openConnection().getHeaderField("Last-Modified"); 599 logger.debug("Last modified date of server file ({}) is {}",url.toString(),lastModified); 600 601 602 if (lastModified!=null) { 603 604 try { 605 date = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH).parse(lastModified); 606 } catch (ParseException e) { 607 logger.warn("Could not parse last modified time from string '{}', no last modified time available for file {}", 608 lastModified, url.toString()); 609 // this will return null 610 } 611 612 } 613 } catch (IOException e) { 614 logger.warn("Problems while retrieving last modified time for file {}", url.toString()); 615 } 616 return date; 617 618 } 619 620 /** 621 * Gets the directory in which the file for a given MMCIF file would live, 622 * creating it if necessary. 623 * 624 * The obsolete parameter is necessary to avoid additional server queries. 625 * @param pdbId 626 * @param obsolete Whether the pdbId is obsolete or not 627 * @return File pointing to the directory, 628 */ 629 protected File getDir(String pdbId, boolean obsolete) { 630 631 File dir = null; 632 int offset = pdbId.length() - 3; 633 634 if (obsolete) { 635 // obsolete is always split 636 String middle = pdbId.substring(offset, offset + 2).toLowerCase(); 637 dir = new File(obsoleteDirPath, middle); 638 } else { 639 String middle = pdbId.substring(offset, offset + 2).toLowerCase(); 640 dir = new File(splitDirPath, middle); 641 } 642 643 644 if (!dir.exists()) { 645 boolean success = dir.mkdirs(); 646 if (!success) logger.error("Could not create mmCIF dir {}",dir.toString()); 647 } 648 649 return dir; 650 } 651 652 /** 653 * Searches for previously downloaded files 654 * @param pdbId 655 * @return A file pointing to the existing file, or null if not found 656 * @throws IOException If the file exists but is empty and can't be deleted 657 */ 658 public File getLocalFile(String pdbId) throws IOException { 659 return getLocalFile(new PdbId(pdbId)); 660 } 661 /** 662 * Searches for previously downloaded files 663 * @param pdbId 664 * @return A file pointing to the existing file, or null if not found 665 * @throws IOException If the file exists but is empty and can't be deleted 666 */ 667 public File getLocalFile(PdbId pdbId) throws IOException { 668 String id = pdbId.getId(); 669 int offset = id.length() - 3; 670 // Search for existing files 671 672 // Search directories: 673 // 1) LOCAL_MMCIF_SPLIT_DIR/<middle>/(pdb)?<pdbId>.<ext> 674 // 2) LOCAL_MMCIF_ALL_DIR/<middle>/(pdb)?<pdbId>.<ext> 675 LinkedList<File> searchdirs = new LinkedList<>(); 676 String middle = id.substring(offset, offset+2).toLowerCase(); 677 678 File splitdir = new File(splitDirPath, middle); 679 searchdirs.add(splitdir); 680 // Search obsolete files if requested 681 if(getObsoleteBehavior() == ObsoleteBehavior.FETCH_OBSOLETE) { 682 File obsdir = new File(obsoleteDirPath,middle); 683 searchdirs.add(obsdir); 684 } 685 686 // valid prefixes before the <pdbId> in the filename 687 String[] prefixes = new String[] {"", "pdb"}; 688 689 for( File searchdir :searchdirs){ 690 for( String prefix : prefixes) { 691 for(String ex : getExtensions() ){ 692 File f = new File(searchdir,prefix + id.toLowerCase() + ex) ; 693 if ( f.exists()) { 694 // delete files that are too short to have contents 695 if( f.length() < MIN_PDB_FILE_SIZE ) { 696 Files.delete(f.toPath()); 697 return null; 698 } 699 return f; 700 } 701 } 702 } 703 } 704 //Not found 705 return null; 706 } 707 708 protected boolean checkFileExists(String pdbId) { 709 return checkFileExists(new PdbId(pdbId)); 710 } 711 protected boolean checkFileExists(PdbId pdbId){ 712 try { 713 File path = getLocalFile(pdbId); 714 if ( path != null) 715 return true; 716 } catch(IOException e) {} 717 return false; 718 } 719 720 /** 721 * Return the String with the PDB server name, including the leading protocol 722 * String (http:// or ftp://). 723 * The server name will be by default the value {@value #DEFAULT_PDB_FILE_SERVER} or the one 724 * read from system property {@value #PDB_FILE_SERVER_PROPERTY} 725 * 726 * @return the server name including the leading protocol string 727 */ 728 public static String getServerName() { 729 String name = System.getProperty(PDB_FILE_SERVER_PROPERTY); 730 731 if ( name == null || name.trim().isEmpty()) { 732 name = DEFAULT_PDB_FILE_SERVER; 733 logger.debug("Using default PDB file server {}",name); 734 } else { 735 if (!name.startsWith("http://") && !name.startsWith("ftp://") && !name.startsWith("https://")) { 736 logger.warn("Server name {} read from system property {} does not have a leading protocol string. Adding http:// to it", name, PDB_FILE_SERVER_PROPERTY); 737 name = "http://"+name; 738 } 739 logger.info("Using PDB file server {} read from system property {}", name, PDB_FILE_SERVER_PROPERTY); 740 } 741 return name; 742 } 743 744 /** 745 * Should be called whenever any of the path variables change. 746 * Thus, if {@link getSplitDirPath()} or {@link getObsoleteDirPath()} 747 * depend on anything, they should call this function when that thing 748 * changes (possibly including at the end of the constructor). 749 */ 750 protected void initPaths() { 751 // Hand-rolled String.join(), for java 6 752 String[] split = getSplitDirPath(); 753 String[] obsolete = getObsoleteDirPath(); 754 755 //URLs are joined with '/' 756 StringBuilder splitURL = new StringBuilder("/pub/pdb"); 757 for(int i=0;i<split.length;i++) { 758 splitURL.append("/"); 759 splitURL.append(split[i]); 760 } 761 StringBuilder obsoleteURL = new StringBuilder("/pub/pdb"); 762 for(int i=0;i<obsolete.length;i++) { 763 obsoleteURL.append("/"); 764 obsoleteURL.append(obsolete[i]); 765 } 766 767 splitDirURL = splitURL.toString(); 768 obsoleteDirURL = obsoleteURL.toString(); 769 770 771 //Files join themselves iteratively 772 splitDirPath = path; 773 for(int i=0;i<split.length;i++) { 774 splitDirPath = new File(splitDirPath,split[i]); 775 } 776 obsoleteDirPath = path; 777 for(int i=0;i<obsolete.length;i++) { 778 obsoleteDirPath = new File(obsoleteDirPath,obsolete[i]); 779 } 780 } 781 782 /** 783 * Converts a PDB ID into a filename with the proper extension 784 * @param pdbId 785 * @return The filename, e.g. "4hhb.pdb.gz" 786 */ 787 protected abstract String getFilename(String pdbId); 788 789 /** 790 * Location of split files within the directory, as an array of paths. 791 * These will be joined with either slashes (for the URL) or the file 792 * separator (for directories). The returned results should be constant, 793 * to allow for caching. 794 * @return A list of directories, relative to the /pub/pdb directory on the server 795 */ 796 protected abstract String[] getSplitDirPath(); 797 /** 798 * Location of obsolete files within the directory, as an array of paths. 799 * These will be joined with either slashes (for the URL) or the file 800 * separator (for directories). The returned results should be constant, 801 * to allow for caching. 802 * @return A list of directories, relative to the /pub/pdb directory on the server 803 */ 804 protected abstract String[] getObsoleteDirPath(); 805}