diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/StructureIO.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/StructureIO.java index 31fe6adb25..a0757cf8f4 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/StructureIO.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/StructureIO.java @@ -119,6 +119,11 @@ private static void checkInitAtomCache() { public static void setAtomCache(AtomCache c){ cache = c; } + + public static AtomCache getAtomCache() { + checkInitAtomCache(); + return cache; + } /** * Returns the first biologicalAssembly that is available for a protein structure. For more documentation on quaternary structures see: diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java index 265248d361..70c55d69d6 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java @@ -636,6 +636,7 @@ private void parseCathDomall(BufferedReader bufferedReader) throws IOException{ protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOException{ // System.out.println("downloading " + remoteURL + " to: " + localFile); + LOGGER.info("Downloading file {} to local file {}", remoteURL, localFile); long timeS = System.currentTimeMillis(); File tempFile = File.createTempFile(FileDownloadUtils.getFilePrefix(localFile), "."+ FileDownloadUtils.getFileExtension(localFile)); @@ -665,7 +666,7 @@ protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOEx disp = disp / 1024.0; } long timeE = System.currentTimeMillis(); - LOGGER.info("Downloaded file {} ({}) to local file {} in {} sec.", remoteURL, String.format("%.1f",disp) + unit, localFile, (timeE - timeS)/1000); + LOGGER.info("Downloaded {} in {} sec. to {}", String.format("%.1f",disp) + unit, (timeE - timeS)/1000, localFile); } private boolean domainDescriptionFileAvailable(){ diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java index a85c3e701a..b01252b380 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.file.Files; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -127,6 +128,9 @@ public static enum FetchBehavior { protected static final String lineSplit = System.getProperty("file.separator"); + /** Minimum size for a valid structure file (CIF or PDB), in bytes */ + public static final long MIN_PDB_FILE_SIZE = 40; // Empty gzip files are 20bytes. Add a few more for buffer. + private File path; private List extensions; @@ -402,8 +406,9 @@ public void prefetchStructure(String pdbId) throws IOException { * Attempts to delete all versions of a structure from the local directory. * @param pdbId * @return True if one or more files were deleted + * @throws IOException if the file cannot be deleted */ - public boolean deleteStructure(String pdbId){ + public boolean deleteStructure(String pdbId) throws IOException{ boolean deleted = false; // Force getLocalFile to check in obsolete locations ObsoleteBehavior obsolete = getObsoleteBehavior(); @@ -421,7 +426,7 @@ public boolean deleteStructure(String pdbId){ // delete file boolean success = existing.delete(); if(success) { - logger.info("Deleting "+existing.getAbsolutePath()); + logger.debug("Deleting "+existing.getAbsolutePath()); } deleted = deleted || success; @@ -430,7 +435,7 @@ public boolean deleteStructure(String pdbId){ if(parent != null) { success = parent.delete(); if(success) { - logger.info("Deleting "+parent.getAbsolutePath()); + logger.debug("Deleting "+parent.getAbsolutePath()); } } @@ -660,8 +665,9 @@ protected File getDir(String pdbId, boolean obsolete) { * Searches for previously downloaded files * @param pdbId * @return A file pointing to the existing file, or null if not found + * @throws IOException If the file exists but is empty and can't be deleted */ - public File getLocalFile(String pdbId) { + public File getLocalFile(String pdbId) throws IOException { // Search for existing files @@ -687,6 +693,11 @@ public File getLocalFile(String pdbId) { for(String ex : getExtensions() ){ File f = new File(searchdir,prefix + pdbId.toLowerCase() + ex) ; if ( f.exists()) { + // delete files that are too short to have contents + if( f.length() < MIN_PDB_FILE_SIZE ) { + Files.delete(f.toPath()); + return null; + } return f; } } @@ -697,9 +708,11 @@ public File getLocalFile(String pdbId) { } protected boolean checkFileExists(String pdbId){ - File path = getLocalFile(pdbId); - if ( path != null) - return true; + try { + File path = getLocalFile(pdbId); + if ( path != null) + return true; + } catch(IOException e) {} return false; } diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/ChemCompGroupFactory.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/ChemCompGroupFactory.java index dd4585fb33..eee59a0a7c 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/ChemCompGroupFactory.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/ChemCompGroupFactory.java @@ -68,9 +68,8 @@ public static ChemComp getChemComp(String recordName){ * again. Note that this change can have unexpected behavior of * code executed afterwards. *

- * Changing the provider does not reset the cache, so Chemical - * Component definitions already downloaded from previous providers - * will be used. To reset the cache see {@link #getCache()). + * Changing the provider also resets the cache, so any groups + * previously accessed will be reread or re-downloaded. * * @param provider */ @@ -84,6 +83,15 @@ public static void setChemCompProvider(ChemCompProvider provider) { public static ChemCompProvider getChemCompProvider(){ return chemCompProvider; } + + /** + * Force the in-memory cache to be reset. + * + * Note that the ChemCompProvider may have additional memory or disk caches that need to be cleared too. + */ + public static void clearCache() { + cache.clear(); + } public static Group getGroupFromChemCompDictionary(String recordName) { diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/DownloadChemCompProvider.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/DownloadChemCompProvider.java index fac2a86cbb..42e7692782 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/DownloadChemCompProvider.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/DownloadChemCompProvider.java @@ -42,6 +42,7 @@ import org.biojava.nbio.core.util.InputStreamProvider; import org.biojava.nbio.structure.align.util.HTTPConnectionTools; import org.biojava.nbio.structure.align.util.UserConfiguration; +import org.biojava.nbio.structure.io.LocalPDBDirectory; import org.biojava.nbio.structure.io.mmcif.model.ChemComp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,6 +86,8 @@ public class DownloadChemCompProvider implements ChemCompProvider { protectedIDs.add("AUX"); protectedIDs.add("NUL"); } + + private static ChemCompProvider fallback = null; // Fallback provider if the download fails /** by default we will download only some of the files. User has to request that all files should be downloaded... * @@ -92,25 +95,28 @@ public class DownloadChemCompProvider implements ChemCompProvider { boolean downloadAll = false; public DownloadChemCompProvider(){ - logger.debug("Initialising DownloadChemCompProvider"); - - // note that path is static, so this is just to make sure that all non-static methods will have path initialised - initPath(); + this(null); } public DownloadChemCompProvider(String cacheFilePath){ logger.debug("Initialising DownloadChemCompProvider"); // note that path is static, so this is just to make sure that all non-static methods will have path initialised - path = new File(cacheFilePath); + if(cacheFilePath != null) { + path = new File(cacheFilePath); + } } - private static void initPath(){ - + /** + * Get this provider's cache path + * @return + */ + public static File getPath(){ if (path==null) { UserConfiguration config = new UserConfiguration(); path = new File(config.getCacheFilePath()); } + return path; } /** @@ -127,7 +133,7 @@ public void checkDoFirstInstall(){ // this makes sure there is a file separator between every component, // if path has a trailing file separator or not, it will work for both cases - File dir = new File(path, CHEM_COMP_CACHE_DIRECTORY); + File dir = new File(getPath(), CHEM_COMP_CACHE_DIRECTORY); File f = new File(dir, "components.cif.gz"); if ( ! f.exists()) { @@ -161,7 +167,7 @@ private void split() throws IOException { logger.info("Installing individual chem comp files ..."); - File dir = new File(path, CHEM_COMP_CACHE_DIRECTORY); + File dir = new File(getPath(), CHEM_COMP_CACHE_DIRECTORY); File f = new File(dir, "components.cif.gz"); @@ -212,7 +218,7 @@ private void split() throws IOException { */ private void writeID(String contents, String currentID) throws IOException{ - String localName = DownloadChemCompProvider.getLocalFileName(currentID); + String localName = getLocalFileName(currentID); try ( PrintWriter pw = new PrintWriter(new GZIPOutputStream(new FileOutputStream(localName))) ) { @@ -272,7 +278,10 @@ public ChemComp getChemComp(String recordName) { ChemComp chemComp = dict.getChemComp(recordName); - return chemComp; + // May be null if the file was corrupt. Fall back on ReducedChemCompProvider in that case + if(chemComp != null) { + return chemComp; + } } catch (IOException e) { @@ -296,9 +305,12 @@ public ChemComp getChemComp(String recordName) { // see https://github.com/biojava/biojava/issues/315 // probably a network error happened. Try to use the ReducedChemCOmpProvider - ReducedChemCompProvider reduced = new ReducedChemCompProvider(); + if( fallback == null) { + fallback = new ReducedChemCompProvider(); + } - return reduced.getChemComp(recordName); + logger.warn("Falling back to ReducedChemCompProvider for {}. This could indicate a network error.", recordName); + return fallback.getChemComp(recordName); } @@ -313,16 +325,15 @@ public static String getLocalFileName(String recordName){ recordName = "_" + recordName; } - initPath(); - - File f = new File(path, CHEM_COMP_CACHE_DIRECTORY); + File f = new File(getPath(), CHEM_COMP_CACHE_DIRECTORY); if (! f.exists()){ logger.info("Creating directory " + f); boolean success = f.mkdir(); // we've checked in initPath that path is writable, so there's no need to check if it succeeds // in the unlikely case that in the meantime it isn't writable at least we log an error - if (!success) logger.error("Directory {} could not be created",f); + if (!success) + logger.error("Directory {} could not be created",f); } @@ -337,6 +348,14 @@ private static boolean fileExists(String recordName){ File f = new File(fileName); + // delete files that are too short to have contents + if( f.length() < LocalPDBDirectory.MIN_PDB_FILE_SIZE ) { + // Delete defensively. + // Note that if delete is unsuccessful, we re-download the file anyways + f.delete(); + return false; + } + return f.exists(); } diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/SimpleMMcifParser.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/SimpleMMcifParser.java index 1b6851c6d8..a02f90139d 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/SimpleMMcifParser.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmcif/SimpleMMcifParser.java @@ -212,6 +212,9 @@ public void parse(BufferedReader buf) // the first line is a data_PDBCODE line, test if this looks like a mmcif file line = buf.readLine(); + while( line != null && (line.isEmpty() || line.startsWith(COMMENT_CHAR))) { + line = buf.readLine(); + } if (line == null || !line.startsWith(MMCIF_TOP_HEADER)){ logger.error("This does not look like a valid mmCIF file! The first line should start with 'data_', but is: '" + line+"'"); triggerDocumentEnd(); diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/util/FileDownloadUtils.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/util/FileDownloadUtils.java index ab8833d716..98adc90b2e 100644 --- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/util/FileDownloadUtils.java +++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/util/FileDownloadUtils.java @@ -21,9 +21,6 @@ */ package org.biojava.nbio.structure.io.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -36,6 +33,15 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FileDownloadUtils { @@ -240,6 +246,41 @@ public static URLConnection prepareURLConnection(String url, int timeout) throws connection.setConnectTimeout(timeout); return connection; } + + /** + * Recursively delete a folder & contents + * + * @param dir directory to delete + */ + public static void deleteDirectory(Path dir) throws IOException { + if(dir == null || !Files.exists(dir)) + return; + Files.walkFileTree(dir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { + if (e != null) { + throw e; + } + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + /** + * Recursively delete a folder & contents + * + * @param dir directory to delete + */ + public static void deleteDirectory(String dir) throws IOException { + deleteDirectory(Paths.get(dir)); + } + public static void main(String[] args) { String url; diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/TestAtomCache.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/TestAtomCache.java index 2c2e8fc431..199f0295d1 100644 --- a/biojava-structure/src/test/java/org/biojava/nbio/structure/TestAtomCache.java +++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/TestAtomCache.java @@ -46,7 +46,7 @@ public class TestAtomCache { private AtomCache cache; @Before - public void setUp() { + public void setUp() throws IOException { cache = new AtomCache(); // Delete files which were cached in previous tests diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/TestExperimentalTechniques.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/TestExperimentalTechniques.java index f9915e47db..25436297a6 100644 --- a/biojava-structure/src/test/java/org/biojava/nbio/structure/TestExperimentalTechniques.java +++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/TestExperimentalTechniques.java @@ -31,7 +31,7 @@ public class TestExperimentalTechniques { @Test - public void test4LNC() throws IOException, StructureException { + public void test6F2Q() throws IOException, StructureException { // a multiple experimental techniques PDB entry (X-RAY + NEUTRON DIFFRACTION) @@ -40,9 +40,9 @@ public void test4LNC() throws IOException, StructureException { StructureIO.setAtomCache(cache); cache.setUseMmCif(false); - Structure sPdb = StructureIO.getStructure("4LNC"); + Structure sPdb = StructureIO.getStructure("6F2Q"); cache.setUseMmCif(true); - Structure sCif = StructureIO.getStructure("4LNC"); + Structure sCif = StructureIO.getStructure("6F2Q"); comparePdbToCif(sPdb, sCif); diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java index 08858f46b1..0cecbb1574 100644 --- a/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java +++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java @@ -20,26 +20,52 @@ */ package org.biojava.nbio.structure.align.util; -import org.biojava.nbio.structure.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.zip.GZIPOutputStream; + +import org.biojava.nbio.structure.AtomPositionMap; +import org.biojava.nbio.structure.Chain; +import org.biojava.nbio.structure.Group; +import org.biojava.nbio.structure.ResidueRangeAndLength; +import org.biojava.nbio.structure.Structure; +import org.biojava.nbio.structure.StructureException; +import org.biojava.nbio.structure.StructureIO; +import org.biojava.nbio.structure.StructureIdentifier; +import org.biojava.nbio.structure.StructureTools; +import org.biojava.nbio.structure.SubstructureIdentifier; import org.biojava.nbio.structure.io.LocalPDBDirectory; import org.biojava.nbio.structure.io.LocalPDBDirectory.FetchBehavior; import org.biojava.nbio.structure.io.LocalPDBDirectory.ObsoleteBehavior; import org.biojava.nbio.structure.io.MMCIFFileReader; +import org.biojava.nbio.structure.io.mmcif.ChemCompGroupFactory; +import org.biojava.nbio.structure.io.mmcif.DownloadChemCompProvider; +import org.biojava.nbio.structure.io.mmcif.model.ChemComp; +import org.biojava.nbio.structure.io.util.FileDownloadUtils; import org.biojava.nbio.structure.scop.ScopDatabase; import org.biojava.nbio.structure.scop.ScopFactory; +import org.biojava.nbio.structure.test.util.GlobalsHelper; import org.junit.After; import org.junit.Before; import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import static org.junit.Assert.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -49,22 +75,24 @@ */ public class AtomCacheTest { + private static Logger logger = LoggerFactory.getLogger(AtomCacheTest.class); private AtomCache cache; - private String previousPDB_DIR; @Before public void setUp() { - previousPDB_DIR = System.getProperty(UserConfiguration.PDB_DIR, null); + GlobalsHelper.pushState(); + cache = new AtomCache(); cache.setObsoleteBehavior(ObsoleteBehavior.FETCH_OBSOLETE); + StructureIO.setAtomCache(cache); + // Use a fixed SCOP version for stability ScopFactory.setScopDatabase(ScopFactory.VERSION_1_75B); } @After public void tearDown() { - if (previousPDB_DIR != null) - System.setProperty(UserConfiguration.PDB_DIR, previousPDB_DIR); + GlobalsHelper.restoreState(); } /** @@ -312,4 +340,125 @@ public void testSeqRes() throws StructureException, IOException { } + /** + * Test for #703 - Chemical component cache poisoning + * + * Handle empty CIF files + * @throws IOException + * @throws StructureException + */ + @Test + public void testEmptyChemComp() throws IOException, StructureException { + Path tmpCache = Paths.get(System.getProperty("java.io.tmpdir"),"BIOJAVA_TEST_CACHE"); + logger.info("Testing AtomCache at {}", tmpCache.toString()); + System.setProperty(UserConfiguration.PDB_DIR, tmpCache.toString()); + System.setProperty(UserConfiguration.PDB_CACHE_DIR, tmpCache.toString()); + + FileDownloadUtils.deleteDirectory(tmpCache); + Files.createDirectory(tmpCache); + try { + cache.setPath(tmpCache.toString()); + cache.setCachePath(tmpCache.toString()); + cache.setUseMmCif(true); + ChemCompGroupFactory.setChemCompProvider(new DownloadChemCompProvider(tmpCache.toString())); + + // Create an empty chemcomp + Path chemCompCif = tmpCache.resolve(Paths.get("chemcomp", "ATP.cif.gz")); + Files.createDirectories(chemCompCif.getParent()); + Files.createFile(chemCompCif); + assertTrue(Files.exists(chemCompCif)); + assertEquals(0, Files.size(chemCompCif)); + + // Copy stub file into place + Path testCif = tmpCache.resolve(Paths.get("data", "structures", "divided", "mmCIF", "ab","1abc.cif.gz")); + Files.createDirectories(testCif.getParent()); + URL resource = AtomCacheTest.class.getResource("/atp.cif.gz"); + File src = new File(resource.getPath()); + FileDownloadUtils.copy(src, testCif.toFile()); + + // Load structure + Structure s = cache.getStructure("1ABC"); + + // Should have re-downloaded the file + assertTrue(Files.size(chemCompCif) > LocalPDBDirectory.MIN_PDB_FILE_SIZE); + + // Structure should have valid ChemComp now + assertNotNull(s); + + Group g = s.getChainByPDB("A").getAtomGroup(0); + assertTrue(g.getPDBName().equals("ATP")); + + // should be unknown + ChemComp chem = g.getChemComp(); + assertNotNull(chem); + assertTrue(chem.getAtoms().size() > 0); + assertEquals("NON-POLYMER", chem.getType()); + } finally { + FileDownloadUtils.deleteDirectory(tmpCache); + } + } + + /** + * Test for #703 - Chemical component cache poisoning + * + * Handle empty CIF files + * @throws IOException + * @throws StructureException + */ + @Test + public void testEmptyGZChemComp() throws IOException, StructureException { + + Path tmpCache = Paths.get(System.getProperty("java.io.tmpdir"),"BIOJAVA_TEST_CACHE"); + logger.info("Testing AtomCache at {}", tmpCache.toString()); + System.setProperty(UserConfiguration.PDB_DIR, tmpCache.toString()); + System.setProperty(UserConfiguration.PDB_CACHE_DIR, tmpCache.toString()); + + FileDownloadUtils.deleteDirectory(tmpCache); + Files.createDirectory(tmpCache); + try { + cache.setPath(tmpCache.toString()); + cache.setCachePath(tmpCache.toString()); + cache.setUseMmCif(true); + ChemCompGroupFactory.setChemCompProvider(new DownloadChemCompProvider(tmpCache.toString())); + + + // Create an empty chemcomp + Path sub = tmpCache.resolve(Paths.get("chemcomp", "ATP.cif.gz")); + Files.createDirectories(sub.getParent()); + try(GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(sub.toFile()))) { + // don't write anything + out.flush(); + } + assertTrue(Files.exists(sub)); + assertTrue(0 < Files.size(sub) && Files.size(sub) < LocalPDBDirectory.MIN_PDB_FILE_SIZE); + + // Copy stub file into place + Path testCif = tmpCache.resolve(Paths.get("data", "structures", "divided", "mmCIF", "ab","1abc.cif.gz")); + Files.createDirectories(testCif.getParent()); + URL resource = AtomCacheTest.class.getResource("/atp.cif.gz"); + File src = new File(resource.getPath()); + FileDownloadUtils.copy(src, testCif.toFile()); + + // Load structure + Structure s = cache.getStructure("1ABC"); + + // Should have re-downloaded the file + assertTrue(Files.size(sub) > LocalPDBDirectory.MIN_PDB_FILE_SIZE); + + // Structure should have valid ChemComp + assertNotNull(s); + + Group g = s.getChainByPDB("A").getAtomGroup(0); + assertTrue(g.getPDBName().equals("ATP")); + + // should be unknown + ChemComp chem = g.getChemComp(); + assertNotNull(chem); + assertTrue(chem.getAtoms().size() > 0); + assertEquals("NON-POLYMER", chem.getType()); + } finally { + FileDownloadUtils.deleteDirectory(tmpCache); + } + } + } diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/test/util/GlobalsHelper.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/test/util/GlobalsHelper.java new file mode 100644 index 0000000000..c756d5fd8a --- /dev/null +++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/test/util/GlobalsHelper.java @@ -0,0 +1,124 @@ +package org.biojava.nbio.structure.test.util; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +import org.biojava.nbio.structure.StructureIO; +import org.biojava.nbio.structure.align.util.AtomCache; +import org.biojava.nbio.structure.align.util.UserConfiguration; +import org.biojava.nbio.structure.io.mmcif.ChemCompGroupFactory; +import org.biojava.nbio.structure.io.mmcif.ChemCompProvider; +import org.biojava.nbio.structure.io.mmcif.DownloadChemCompProvider; +import org.biojava.nbio.structure.scop.ScopDatabase; +import org.biojava.nbio.structure.scop.ScopFactory; + +/** + * Helper class to manage all the global state changes in BioJava. + * For instance, this should be used in tests before modifying PDB_PATH. + * + * Used by tests during setup and teardown to ensure a clean environment + * + * This class is a singleton. + * @author Spencer Bliven + * + */ +public final class GlobalsHelper { + + private static class PathInfo { + public final String pdbPath; + public final String pdbCachePath; + public final AtomCache atomCache; + public final ChemCompProvider chemCompProvider; + public final String downloadChemCompProviderPath; + public final ScopDatabase scop; + + public PathInfo() { + pdbPath = System.getProperty(UserConfiguration.PDB_DIR, null); + pdbCachePath = System.getProperty(UserConfiguration.PDB_CACHE_DIR, null); + atomCache = StructureIO.getAtomCache(); + chemCompProvider = ChemCompGroupFactory.getChemCompProvider(); + downloadChemCompProviderPath = DownloadChemCompProvider.getPath().getPath(); + scop = ScopFactory.getSCOP(); + } + } + + // Saves defaults as stack + private static Deque stack = new LinkedList<>(); + static { + // Save default state + pushState(); + } + + /** + * GlobalsHelper should not be instantiated. + */ + private GlobalsHelper() {} + + /** + * Save current global state to the stack + */ + public static void pushState() { + PathInfo paths = new PathInfo(); + stack.addFirst(paths); + } + + /** + * Sets a new PDB_PATH and PDB_CACHE_PATH consistently. + * + * Previous values can be restored with {@link #restoreState()}. + * @param path + */ + public static void setPdbPath(String path, String cachePath) { + pushState(); + if(path == null || cachePath == null) { + UserConfiguration config = new UserConfiguration(); + if(path == null) { + path = config.getPdbFilePath(); + } + if(cachePath == null) { + cachePath = config.getCacheFilePath(); + } + } + System.setProperty(UserConfiguration.PDB_DIR, path); + System.setProperty(UserConfiguration.PDB_CACHE_DIR, path); + + AtomCache cache = new AtomCache(path); + StructureIO.setAtomCache(cache); + + // Note side effect setting the path for all DownloadChemCompProvider due to static state + ChemCompProvider provider = new DownloadChemCompProvider(path); + ChemCompGroupFactory.setChemCompProvider(provider); + } + + /** + * Restore global state to the previous settings + * @throws NoSuchElementException if there is no prior state to restore + */ + public static void restoreState() { + PathInfo paths = stack.removeFirst(); + + if(paths.pdbPath == null) { + System.clearProperty(UserConfiguration.PDB_DIR); + } else { + System.setProperty(UserConfiguration.PDB_DIR, paths.pdbPath); + } + if(paths.pdbCachePath == null) { + System.clearProperty(UserConfiguration.PDB_CACHE_DIR); + } else { + System.setProperty(UserConfiguration.PDB_CACHE_DIR, paths.pdbCachePath); + } + + StructureIO.setAtomCache(paths.atomCache); + + // Use side effect setting the path for all DownloadChemCompProvider due to static state + new DownloadChemCompProvider(paths.downloadChemCompProviderPath); + + ChemCompGroupFactory.setChemCompProvider(paths.chemCompProvider); + + ScopFactory.setScopDatabase(paths.scop); + } + + +} diff --git a/biojava-structure/src/test/resources/atp.cif.gz b/biojava-structure/src/test/resources/atp.cif.gz new file mode 100644 index 0000000000..7167313a22 Binary files /dev/null and b/biojava-structure/src/test/resources/atp.cif.gz differ