|
| 1 | +/** |
| 2 | + * BioJava development code |
| 3 | + * |
| 4 | + * This code may be freely distributed and modified under the terms of the GNU |
| 5 | + * Lesser General Public Licence. This should be distributed with the code. If |
| 6 | + * you do not have a copy, see: |
| 7 | + * |
| 8 | + * http://www.gnu.org/copyleft/lesser.html |
| 9 | + * |
| 10 | + * Copyright for this code is held jointly by the individual authors. These |
| 11 | + * should be listed in @author doc comments. |
| 12 | + * |
| 13 | + * For more information on the BioJava project and its aims, or to join the |
| 14 | + * biojava-l mailing list, visit the home page at: |
| 15 | + * |
| 16 | + * http://www.biojava.org/ |
| 17 | + */ |
| 18 | +package org.biojava.nbio.structure.test.io.density; |
| 19 | + |
| 20 | +import static org.junit.Assert.assertEquals; |
| 21 | +import static org.junit.Assert.assertFalse; |
| 22 | +import static org.junit.Assert.assertNotNull; |
| 23 | +import static org.junit.Assert.assertTrue; |
| 24 | +import static org.junit.Assert.fail; |
| 25 | + |
| 26 | +import java.io.File; |
| 27 | +import java.io.IOException; |
| 28 | +import java.nio.file.Files; |
| 29 | +import java.util.Arrays; |
| 30 | +import java.util.List; |
| 31 | + |
| 32 | +import org.biojava.nbio.core.util.FileDownloadUtils; |
| 33 | +import org.biojava.nbio.structure.PdbId; |
| 34 | +import org.biojava.nbio.structure.io.density.Ccp4Header; |
| 35 | +import org.biojava.nbio.structure.io.density.DensityFileFormat; |
| 36 | +import org.biojava.nbio.structure.io.density.DensityMapCache; |
| 37 | +import org.biojava.nbio.structure.io.density.DensityMapKind; |
| 38 | +import org.biojava.nbio.structure.io.density.DensityMapRequest; |
| 39 | +import org.biojava.nbio.structure.io.density.DensityMapResult; |
| 40 | +import org.biojava.nbio.structure.io.density.DensityMapSource; |
| 41 | +import org.biojava.nbio.structure.io.density.NoDensityMapException; |
| 42 | +import org.junit.After; |
| 43 | +import org.junit.Before; |
| 44 | +import org.junit.Test; |
| 45 | + |
| 46 | +/** |
| 47 | + * Density fetching against the real services. |
| 48 | + * <p> |
| 49 | + * Deliberately frugal: the entries chosen keep a full run to a couple of |
| 50 | + * megabytes plus a few small metadata calls. In particular the cryo-EM path is |
| 51 | + * exercised with the size limit set so low that the 116 MB map is declined |
| 52 | + * before any of its body is transferred, which tests the whole resolution and |
| 53 | + * guard sequence without the download. |
| 54 | + * |
| 55 | + * @author Amr ALHOSSARY |
| 56 | + * @since 7.3.0 |
| 57 | + */ |
| 58 | +public class DensityMapIntegrationTest { |
| 59 | + |
| 60 | + private File cacheRoot; |
| 61 | + private DensityMapCache cache; |
| 62 | + |
| 63 | + @Before |
| 64 | + public void setUp() throws IOException { |
| 65 | + cacheRoot = Files.createTempDirectory("bj-density-it").toFile(); |
| 66 | + cache = new DensityMapCache(cacheRoot.getAbsolutePath()); |
| 67 | + } |
| 68 | + |
| 69 | + @After |
| 70 | + public void tearDown() throws IOException { |
| 71 | + FileDownloadUtils.deleteDirectory(cacheRoot.toPath()); |
| 72 | + } |
| 73 | + |
| 74 | + /** The default path for an X-ray entry: the smallest source answers first. */ |
| 75 | + @Test |
| 76 | + public void fetchesAnXrayMapFromTheFirstSourceTried() throws IOException { |
| 77 | + DensityMapResult result = cache.getDensityMap(new PdbId("1cbs"), DensityMapKind.TWO_FO_FC); |
| 78 | + |
| 79 | + assertEquals(DensityMapSource.RCSB_VOLUME_SERVER, result.getSource()); |
| 80 | + assertEquals(DensityMapKind.TWO_FO_FC, result.getKind()); |
| 81 | + assertTrue(result.isRenderable()); |
| 82 | + assertFalse(result.isFromCache()); |
| 83 | + assertTrue(result.getFileSizeBytes() > 1024); |
| 84 | + assertTrue("a .meta sidecar makes the result reconstructible offline", |
| 85 | + DensityMapResult.metaFileFor(result.getFile()).isFile()); |
| 86 | + |
| 87 | + // second call must come from the cache without another download |
| 88 | + DensityMapResult again = cache.getDensityMap(new PdbId("1cbs"), DensityMapKind.TWO_FO_FC); |
| 89 | + assertTrue(again.isFromCache()); |
| 90 | + assertEquals(result.getFile(), again.getFile()); |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * Both map kinds come out of one download, and the difference map is presented |
| 95 | + * under the companion name that makes Jmol read the other data block. |
| 96 | + */ |
| 97 | + @Test |
| 98 | + public void bothKindsShareASingleDownload() throws IOException { |
| 99 | + DensityMapResult twoFoFc = cache.getDensityMap(new PdbId("1cbs"), DensityMapKind.TWO_FO_FC); |
| 100 | + DensityMapResult foFc = cache.getDensityMap(new PdbId("1cbs"), DensityMapKind.FO_FC); |
| 101 | + |
| 102 | + assertEquals(DensityMapKind.FO_FC, foFc.getKind()); |
| 103 | + assertFalse("the difference map needs its own file name", twoFoFc.getFile().equals(foFc.getFile())); |
| 104 | + assertTrue("the marker has to be in the name for Jmol to select the FO-FC block", |
| 105 | + foFc.getFile().getName().contains("&diff=1")); |
| 106 | + assertEquals("both names must address the same bytes", |
| 107 | + twoFoFc.getFileSizeBytes(), foFc.getFileSizeBytes()); |
| 108 | + } |
| 109 | + |
| 110 | + /** PDBe serves real CCP4 files, which the header check should recognise. */ |
| 111 | + @Test |
| 112 | + public void pdbeServesAGenuineCcp4Map() throws IOException { |
| 113 | + cache.setSourceChain(DensityMapKind.TWO_FO_FC, Arrays.asList(DensityMapSource.PDBE_CCP4)); |
| 114 | + DensityMapResult result = cache.getDensityMap(new PdbId("1cbs"), DensityMapKind.TWO_FO_FC); |
| 115 | + |
| 116 | + assertEquals(DensityMapSource.PDBE_CCP4, result.getSource()); |
| 117 | + assertEquals(DensityFileFormat.CCP4, result.getFormat()); |
| 118 | + assertTrue("the CCP4 stamp should be present at byte 208", Ccp4Header.isCcp4(result.getFile())); |
| 119 | + assertTrue(FileDownloadUtils.validateFile(result.getFile())); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * The whole cryo-EM route: resolve the EMDB entry, pick up the author contour |
| 124 | + * level, and decline the full map on size without transferring it. |
| 125 | + */ |
| 126 | + @Test |
| 127 | + public void resolvesCryoEmEntriesAndHonoursTheSizeLimit() throws IOException { |
| 128 | + List<String> emdbIds = cache.getEmdbResolver().getEmdbIds(new PdbId("6hu9")); |
| 129 | + assertEquals(Arrays.asList("EMD-0262"), emdbIds); |
| 130 | + |
| 131 | + DensityMapResult result = cache.getDensityMap(new PdbId("6hu9"), DensityMapKind.AUTO); |
| 132 | + assertEquals(DensityMapKind.EM, result.getKind()); |
| 133 | + assertEquals("EMD-0262", result.getEmdbId()); |
| 134 | + assertNotNull("EM maps need the author contour level to be displayed properly", |
| 135 | + result.getRecommendedContourLevel()); |
| 136 | + assertEquals(0.0263, result.getRecommendedContourLevel(), 1e-6); |
| 137 | + assertNotNull(result.getContourInSigma()); |
| 138 | + |
| 139 | + // With only the full archive enabled and a tiny ceiling, the guard must fire |
| 140 | + // rather than pulling down 116 MB. |
| 141 | + DensityMapCache strict = new DensityMapCache(cacheRoot.getAbsolutePath()); |
| 142 | + strict.setSourceChain(DensityMapKind.EM, Arrays.asList(DensityMapSource.EMDB_MAP)); |
| 143 | + strict.setMaxDownloadBytes(1024); |
| 144 | + try { |
| 145 | + strict.getDensityMap(DensityMapRequest.builder(new PdbId("6hu9")).kind(DensityMapKind.EM).build()); |
| 146 | + fail("the size guard should have declined the full EMDB map"); |
| 147 | + } catch (NoDensityMapException e) { |
| 148 | + assertTrue(e.getAttempts().get(DensityMapSource.EMDB_MAP).contains("too large")); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + /** 4HHB was deposited in 1984 without structure factors, so nothing has a map for it. */ |
| 153 | + @Test |
| 154 | + public void reportsWhyAnEntryHasNoDensity() throws IOException { |
| 155 | + cache.setSourceEnabled(DensityMapSource.WWPDB_MAP_COEFFICIENTS, true); |
| 156 | + try { |
| 157 | + cache.getDensityMap(new PdbId("4hhb"), DensityMapKind.AUTO); |
| 158 | + fail("4hhb has no deposited structure factors"); |
| 159 | + } catch (NoDensityMapException e) { |
| 160 | + assertFalse(e.getAttempts().isEmpty()); |
| 161 | + assertTrue(e.getAttempts().values().stream().anyMatch(r -> r.contains("404"))); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * The wwPDB servers return the content MD5 as the ETag, so a coefficient |
| 167 | + * download is checksum-verified without a separate hash file. |
| 168 | + */ |
| 169 | + @Test |
| 170 | + public void mapCoefficientsArriveWithAVerifiableChecksum() throws IOException { |
| 171 | + cache.setSourceEnabled(DensityMapSource.WWPDB_MAP_COEFFICIENTS, true); |
| 172 | + cache.setSourceChain(DensityMapKind.TWO_FO_FC, Arrays.asList(DensityMapSource.WWPDB_MAP_COEFFICIENTS)); |
| 173 | + |
| 174 | + DensityMapResult result = cache.getDensityMap(DensityMapRequest.builder(new PdbId("1cbs")) |
| 175 | + .kind(DensityMapKind.TWO_FO_FC) |
| 176 | + .allowNonRenderableFormats(true) |
| 177 | + .build()); |
| 178 | + |
| 179 | + assertEquals(DensityMapSource.WWPDB_MAP_COEFFICIENTS, result.getSource()); |
| 180 | + assertFalse("structure factors are not a map and must not claim to be renderable", |
| 181 | + result.isRenderable()); |
| 182 | + |
| 183 | + File hashFile = new File(result.getFile().getParentFile(), result.getFile().getName() + ".hash_MD5"); |
| 184 | + assertTrue("an MD5 should have been recorded from the ETag", hashFile.isFile()); |
| 185 | + assertTrue(FileDownloadUtils.validateFile(result.getFile())); |
| 186 | + |
| 187 | + // corrupt it and confirm the checksum actually catches it |
| 188 | + Files.write(result.getFile().toPath(), new byte[] {0, 1, 2, 3}); |
| 189 | + assertFalse(FileDownloadUtils.validateFile(result.getFile())); |
| 190 | + } |
| 191 | +} |
0 commit comments