From 69fc0ad6962893fc2df777dcc2dba5ba16b7ef25 Mon Sep 17 00:00:00 2001 From: Jeanette Clark Date: Mon, 8 Apr 2024 15:04:45 -0700 Subject: [PATCH 1/4] use try with resources pattern --- .github/workflows/build.yml | 2 +- pom.xml | 6 +- .../edu/ucsb/nceas/mdq/rest/RunsResource.java | 82 +++++++------- .../ucsb/nceas/mdq/rest/SuitesResource.java | 105 +++++++----------- 4 files changed, 84 insertions(+), 111 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80b21c2..b990cb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: docker-publish: name: Docker Build and Publish - if: github.ref_name == 'develop' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref_name, 'feature') + if: github.ref_name == 'develop' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref_name, 'bugfix') needs: maven-build runs-on: ubuntu-latest permissions: diff --git a/pom.xml b/pom.xml index 4e80f50..8bbe823 100644 --- a/pom.xml +++ b/pom.xml @@ -6,8 +6,8 @@ UTF-8 metadig - 3.0.0 - 3.0.0 + 3.0.1-SNAPSHOT + 3.0.1-SNAPSHOT 4.0.0 @@ -15,7 +15,7 @@ edu.ucsb.nceas metadig-webapp war - 3.0.0 + 3.0.1-SNAPSHOT metadig-webapp diff --git a/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java b/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java index 771c063..d10a295 100644 --- a/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java +++ b/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java @@ -12,6 +12,7 @@ import edu.ucsb.nceas.mdqengine.exception.MetadigException; import edu.ucsb.nceas.mdqengine.exception.MetadigStoreException; import edu.ucsb.nceas.mdqengine.store.StoreFactory; +import edu.ucsb.nceas.mdqengine.store.DatabaseStore; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -26,71 +27,63 @@ @Path("runs") public class RunsResource { - private Log log = LogFactory.getLog(this.getClass()); + private Log log = LogFactory.getLog(this.getClass()); + + public RunsResource() {} - public RunsResource() { - } - /** - * Method handling HTTP GET requests. The returned object will be sent - * to the client as "text/plain" media type. + * Method handling HTTP GET requests. The returned object will be sent to the client as + * "text/plain" media type. * * @return String that will be returned as a text/plain response. */ -// @GET -// @Produces(MediaType.APPLICATION_JSON) + // @GET + // @Produces(MediaType.APPLICATION_JSON) public String listRuns() { // persist = true causes a database based store to be created by the factory. - boolean persist = true; - MDQStore store = null; - try { - store = StoreFactory.getStore(persist); + Collection runs = null; + try (DatabaseStore store = new DatabaseStore()) { + runs = store.listRuns(); } catch (MetadigException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); - throw(ise); + throw (ise); } - - Collection runs = store.listRuns(); - store.shutdown(); return JsonMarshaller.toJson(runs); } - + @GET @Path("/{suite}/{id : .+}") // Allow for '/' in the metadataId - @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) - public Response getRun(@PathParam("suite") String suiteId, @PathParam("id") String metadataId, @Context Request r) throws UnsupportedEncodingException, JAXBException { - - boolean persist = true; - MDQStore store = null; - try { - store = StoreFactory.getStore(persist); - } catch (MetadigException e) { - InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); - throw(ise); - } - // Decode just the pid portion of the URL - try { - metadataId = java.net.URLDecoder.decode(metadataId, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - // not going to happen - value came from JDK's own StandardCharsets - } + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + public Response getRun(@PathParam("suite") String suiteId, @PathParam("id") String metadataId, + @Context Request r) throws UnsupportedEncodingException, JAXBException { Run run = null; - log.debug("Getting run for suiteId: " + suiteId + ", metadataId: " + metadataId); - try { - run = store.getRun(metadataId, suiteId); - } catch (MetadigStoreException e) { + try (DatabaseStore store = new DatabaseStore()) { + + // Decode just the pid portion of the URL + try { + metadataId = java.net.URLDecoder.decode(metadataId, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // not going to happen - value came from JDK's own StandardCharsets + } + + log.debug("Getting run for suiteId: " + suiteId + ", metadataId: " + metadataId); + try { + run = store.getRun(metadataId, suiteId); + } catch (MetadigStoreException e) { + InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); + throw (ise); + } + } catch (MetadigException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); - throw(ise); - } finally { - store.shutdown(); + throw (ise); } - if(run != null) { + if (run != null) { log.debug("Retrieved run with pid: " + run.getId()); } else { log.info("Run not retrieved for suiteId: " + suiteId + ", metadataId: " + metadataId); - if(run == null) { + if (run == null) { return Response.status(Response.Status.NOT_FOUND).build(); } } @@ -98,7 +91,8 @@ public Response getRun(@PathParam("suite") String suiteId, @PathParam("id") Stri // Get the HTML request 'Accept' header specified media type and return that type String resultString = null; List vs = - Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).build(); + Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE) + .build(); Variant v = r.selectVariant(vs); if (v == null) { return Response.notAcceptable(vs).build(); diff --git a/src/main/java/edu/ucsb/nceas/mdq/rest/SuitesResource.java b/src/main/java/edu/ucsb/nceas/mdq/rest/SuitesResource.java index 573db49..6958220 100644 --- a/src/main/java/edu/ucsb/nceas/mdq/rest/SuitesResource.java +++ b/src/main/java/edu/ucsb/nceas/mdq/rest/SuitesResource.java @@ -16,6 +16,7 @@ import edu.ucsb.nceas.mdqengine.exception.MetadigException; import edu.ucsb.nceas.mdqengine.store.StoreFactory; +import edu.ucsb.nceas.mdqengine.store.DatabaseStore; import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -66,7 +67,6 @@ public String listSuites() { } Collection suites = store.listSuites(); - store.shutdown(); return JsonMarshaller.toJson(suites); } @@ -84,7 +84,6 @@ public String getSuite(@PathParam("id") String id) throw (ise); } Suite suite = store.getSuite(id); - store.shutdown(); return XmlMarshaller.toXml(suite, true); } @@ -92,26 +91,20 @@ public String getSuite(@PathParam("id") String id) // @Consumes(MediaType.MULTIPART_FORM_DATA) // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 public boolean createSuite(@FormDataParam("suite") InputStream xml) { - boolean persist = true; - MDQStore store = null; - MDQEngine engine = null; - try { - store = StoreFactory.getStore(persist); - engine = new MDQEngine(); - } catch (MetadigException | IOException | ConfigurationException e) { + + try (DatabaseStore store = new DatabaseStore()) { + Suite suite = null; + try { + suite = (Suite) XmlMarshaller.fromXml(IOUtils.toString(xml, "UTF-8"), Suite.class); + store.createSuite(suite); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } catch (MetadigException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); throw (ise); } - Suite suite = null; - try { - suite = (Suite) XmlMarshaller.fromXml(IOUtils.toString(xml, "UTF-8"), Suite.class); - store.createSuite(suite); - } catch (Exception e) { - log.error(e.getMessage(), e); - return false; - } finally { - store.shutdown(); - } return true; } @@ -121,26 +114,20 @@ public boolean createSuite(@FormDataParam("suite") InputStream xml) { // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 public boolean updateSuite(@PathParam("id") String id, @FormDataParam("suite") InputStream xml) throws JAXBException, IOException { - boolean persist = true; - MDQStore store = null; - MDQEngine engine = null; - try { - store = StoreFactory.getStore(persist); - engine = new MDQEngine(); - } catch (MetadigException | IOException | ConfigurationException e) { + + try (DatabaseStore store = new DatabaseStore()) { + Suite suite = null; + try { + suite = (Suite) XmlMarshaller.fromXml(IOUtils.toString(xml, "UTF-8"), Suite.class); + store.updateSuite(suite); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } catch (MetadigException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); throw (ise); } - Suite suite = null; - try { - suite = (Suite) XmlMarshaller.fromXml(IOUtils.toString(xml, "UTF-8"), Suite.class); - store.updateSuite(suite); - } catch (Exception e) { - log.error(e.getMessage(), e); - return false; - } finally { - store.shutdown(); - } return true; } @@ -149,19 +136,14 @@ public boolean updateSuite(@PathParam("id") String id, @FormDataParam("suite") I // @Produces(MediaType.TEXT_PLAIN) // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 public boolean deleteSuite(@PathParam("id") String id) { - boolean persist = true; - MDQStore store = null; - MDQEngine engine = null; - try { - store = StoreFactory.getStore(persist); - engine = new MDQEngine(); - } catch (MetadigException | IOException | ConfigurationException e) { + + try (DatabaseStore store = new DatabaseStore()) { + Suite suite = store.getSuite(id); + store.deleteSuite(suite); + } catch (MetadigException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); throw (ise); } - Suite suite = store.getSuite(id); - store.deleteSuite(suite); - store.shutdown(); return true; } @@ -179,8 +161,6 @@ public Response run(@PathParam("id") String id, // id is the metadig suite id // ("high", "medium", "low") @Context Request r) throws UnsupportedEncodingException, JAXBException { - boolean persist = true; - MDQStore store = null; MDQEngine engine = null; if (priority == null) @@ -224,26 +204,25 @@ public Response run(@PathParam("id") String id, // id is the metadig suite id // to the processing queue. if (priority.equals("high")) { - try { - store = StoreFactory.getStore(persist); + try (DatabaseStore store = new DatabaseStore()) { engine = new MDQEngine(); + + try { + log.info("Running suite " + id + " for pid " + + sysMeta.getIdentifier().getValue()); + Map params = new HashMap(); + Suite suite = store.getSuite(id); + run = engine.runSuite(suite, input, params, sysMeta); + store.createRun(run); + Dispatcher.getDispatcher("python").close(); + } catch (Exception e) { + log.error(e.getMessage(), e); + return Response.serverError().entity(e).build(); + } } catch (MetadigException | IOException | ConfigurationException e) { InternalServerErrorException ise = new InternalServerErrorException(e.getMessage()); throw (ise); } - try { - log.info("Running suite " + id + " for pid " + sysMeta.getIdentifier().getValue()); - Map params = new HashMap(); - Suite suite = store.getSuite(id); - run = engine.runSuite(suite, input, params, sysMeta); - store.createRun(run); - Dispatcher.getDispatcher("python").close(); - } catch (Exception e) { - log.error(e.getMessage(), e); - return Response.serverError().entity(e).build(); - } finally { - store.shutdown(); - } // determine the format of plot to return List vs = Variant From 4b559f241b995ef6144dc573eb71001beaf2b7f7 Mon Sep 17 00:00:00 2001 From: Jeanette Clark Date: Fri, 12 Apr 2024 09:49:41 -0700 Subject: [PATCH 2/4] fix formatting --- .../ucsb/nceas/mdq/rest/ChecksResource.java | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/src/main/java/edu/ucsb/nceas/mdq/rest/ChecksResource.java b/src/main/java/edu/ucsb/nceas/mdq/rest/ChecksResource.java index bddede7..e489886 100644 --- a/src/main/java/edu/ucsb/nceas/mdq/rest/ChecksResource.java +++ b/src/main/java/edu/ucsb/nceas/mdq/rest/ChecksResource.java @@ -48,13 +48,13 @@ */ @Path("checks") public class ChecksResource { - + private Log log = LogFactory.getLog(this.getClass()); - + private MDQStore store = null; - + private MDQEngine engine = null; - + public ChecksResource() throws MetadigStoreException { boolean persist = false; this.store = StoreFactory.getStore(persist); @@ -66,10 +66,10 @@ public ChecksResource() throws MetadigStoreException { log.error(e.getMessage(), e); } } - + /** - * Method handling HTTP GET requests. The returned object will be sent - * to the client as "text/plain" media type. + * Method handling HTTP GET requests. The returned object will be sent to the client as + * "text/plain" media type. * * @return String that will be returned as a text/plain response. */ @@ -79,18 +79,19 @@ public String listChecks() { Collection checks = store.listChecks(); return JsonMarshaller.toJson(checks); } - + @GET @Path("/{id}") @Produces(MediaType.TEXT_XML) - public String getCheck(@PathParam("id") String id) throws UnsupportedEncodingException, JAXBException { + public String getCheck(@PathParam("id") String id) + throws UnsupportedEncodingException, JAXBException { Check check = store.getCheck(id); return (String) XmlMarshaller.toXml(check, true); } - -// @POST -// @Consumes(MediaType.MULTIPART_FORM_DATA) -// not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 + + // @POST + // @Consumes(MediaType.MULTIPART_FORM_DATA) + // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 public boolean createCheck(@FormDataParam("check") InputStream xml) { Check check = null; try { @@ -99,15 +100,16 @@ public boolean createCheck(@FormDataParam("check") InputStream xml) { } catch (Exception e) { log.error(e.getMessage(), e); return false; - } + } return true; } - -// @PUT -// @Path("/{id}") -// @Consumes(MediaType.MULTIPART_FORM_DATA) -// not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 - public boolean updateCheck(@PathParam("id") String id, @FormDataParam("check") InputStream xml) throws JAXBException, IOException { + + // @PUT + // @Path("/{id}") + // @Consumes(MediaType.MULTIPART_FORM_DATA) + // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 + public boolean updateCheck(@PathParam("id") String id, @FormDataParam("check") InputStream xml) + throws JAXBException, IOException { Check check = null; try { check = (Check) XmlMarshaller.fromXml(IOUtils.toString(xml, "UTF-8"), Check.class); @@ -115,38 +117,37 @@ public boolean updateCheck(@PathParam("id") String id, @FormDataParam("check") I } catch (Exception e) { log.error(e.getMessage(), e); return false; - } + } return true; } - -// @DELETE -// @Path("/{id}") -// @Produces(MediaType.TEXT_PLAIN) -// not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 + + // @DELETE + // @Path("/{id}") + // @Produces(MediaType.TEXT_PLAIN) + // not enabled for security reasons, see: https://github.com/NCEAS/metadig-webapp/issues/21 public boolean updateCheck(@PathParam("id") String id) { Check check = store.getCheck(id); store.deleteCheck(check); return true; } - + @POST @Path("/{id}/run") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response run( - @PathParam("id") String id, - @FormDataParam("document") InputStream input, - @FormDataParam("systemMetadata") InputStream sysMetaStream, - @Context Request r) throws UnsupportedEncodingException, JAXBException { - + public Response run(@PathParam("id") String id, @FormDataParam("document") InputStream input, + @FormDataParam("systemMetadata") InputStream sysMetaStream, @Context Request r) + throws UnsupportedEncodingException, JAXBException { + Run run = null; // include SM if it was provided SystemMetadata sysMeta = null; if (sysMetaStream != null) { try { - sysMeta = TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysMetaStream); - } catch (InstantiationException | IllegalAccessException - | IOException | MarshallingException e) { + sysMeta = + TypeMarshaller.unmarshalTypeFromStream(SystemMetadata.class, sysMetaStream); + } catch (InstantiationException | IllegalAccessException | IOException + | MarshallingException e) { log.warn("Could not unmarshall SystemMetadata from stream", e); } } @@ -159,12 +160,13 @@ public Response run( } catch (Exception e) { log.error(e.getMessage(), e); return Response.serverError().entity(e).build(); - } - + } + // determine the format of plot to return String resultString = null; - List vs = - Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).build(); + List vs = + Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE) + .build(); Variant v = r.selectVariant(vs); if (v == null) { return Response.notAcceptable(vs).build(); @@ -176,7 +178,7 @@ public Response run( resultString = JsonMarshaller.toJson(run); } } - + return Response.ok(resultString).build(); } } From f06fa11d248ea9f6f1decd56419423779390bde9 Mon Sep 17 00:00:00 2001 From: Jeanette Clark Date: Fri, 12 Apr 2024 09:49:59 -0700 Subject: [PATCH 3/4] remove redundant if --- src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java b/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java index d10a295..b7449c5 100644 --- a/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java +++ b/src/main/java/edu/ucsb/nceas/mdq/rest/RunsResource.java @@ -83,9 +83,7 @@ public Response getRun(@PathParam("suite") String suiteId, @PathParam("id") Stri log.debug("Retrieved run with pid: " + run.getId()); } else { log.info("Run not retrieved for suiteId: " + suiteId + ", metadataId: " + metadataId); - if (run == null) { - return Response.status(Response.Status.NOT_FOUND).build(); - } + return Response.status(Response.Status.NOT_FOUND).build(); } // Get the HTML request 'Accept' header specified media type and return that type From 30cd7bfe82cdc6bdc80e82cebc05b97f2ec4df74 Mon Sep 17 00:00:00 2001 From: Jeanette Clark Date: Fri, 3 May 2024 12:55:00 -0700 Subject: [PATCH 4/4] update test files --- src/test/resources/test-api.sh | 6 +- src/test/resources/testfile.txt | 230 ++------------------------------ 2 files changed, 14 insertions(+), 222 deletions(-) mode change 100644 => 100755 src/test/resources/test-api.sh diff --git a/src/test/resources/test-api.sh b/src/test/resources/test-api.sh old mode 100644 new mode 100755 index 405fe2a..5862c73 --- a/src/test/resources/test-api.sh +++ b/src/test/resources/test-api.sh @@ -1,15 +1,15 @@ #!/bin/bash # API endpoint URL for testing a check or suite -check_url="http://localhost:8080/metadig-webapp-3.0.0/checks/resource.keywords.controlled-2.0.0/run/" -suite_url="http://localhost:8080/metadig-webapp-3.0.0/suites/FAIR-suite-0.4.0/run/" +check_url="http://localhost:8080/metadig-webapp-3.0.0/checks/check.nsf.award.numbers.in.nsf.database-2.0.0/run/" +#suite_url="http://localhost:8080/metadig-webapp-3.0.0/suites/FAIR-suite-0.4.0/run/" # Headers headers=( "Content-Type: multipart/mixed" ) # Make the API request using curl -curl -X POST "$suite_url" \ +curl -X POST "$check_url" \ --header 'Content-Type: multipart/form-data; boundary=---------BOUNDARY' \ --data-binary @testfile.txt diff --git a/src/test/resources/testfile.txt b/src/test/resources/testfile.txt index 1f751c7..75541de 100644 --- a/src/test/resources/testfile.txt +++ b/src/test/resources/testfile.txt @@ -332,229 +332,21 @@ Content-Type: application/xml;charset=UTF-8 - - Fairbanks sulfate isotope measurements during ALPACA - + + Effective grain size and ice layer data from the top 10 m of GreenTrACS firn cores, western Greenland Ice Sheet + - Allison - Moon + Ian + McDowell - Department of Atmospheric Sciences, University of Washington, Seattle - Graduate Student + University of Nevada, Reno principalInvestigator - - - Ursula - Jongebloed - - Department of Atmospheric Sciences, University of Washington, Seattle - Graduate Student - principalInvestigator - - - - Kayane - Dingilian - - – School of Earth and Atmospheric Sciences, Georgia Institute of Technology, Atlanta - Postdoctoral researcher - principalInvestigator - - - - Andrew - Schauer - - Department of Earth and Space Sciences, University of Washington, Seattle - Research Scientist - principalInvestigator - - - - Yuk-Chun - Chan - - Department of Atmospheric Sciences, University of Washington, Seattle - Graduate Student - principalInvestigator - - - - Meeta - Cesler-Maloney - - Department of Chemistry, Biochemistry, and Geophysical Institute, University of Alaska Fairbanks - Graduate Student - principalInvestigator - - - - William - Simpson - - Department of Chemistry, Biochemistry, and Geophysical Institute, University of Alaska Fairbanks - Professor - principalInvestigator - - - - Rodney - Weber - - – School of Earth and Atmospheric Sciences, Georgia Institute of Technology, Atlanta - Professor - principalInvestigator - - - - Ling - Tsiang - - Department of Atmospheric Sciences, University of Washington, Seattle - Undergraduate Student - principalInvestigator - - - - Fouad - Yazbeck - - Department of Atmospheric Sciences, University of Washington, Seattle - Undergraduate Student - principalInvestigator - - - - Shuting - Zhai - - Department of Atmospheric Sciences, University of Washington, Seattle - Graduate Student - principalInvestigator - - - - Alanna - Wedum - - Department of Atmospheric Sciences, University of Washington, Seattle - Undergraduate Student - principalInvestigator - - - - Alexander - Turner - - Department of Atmospheric Sciences, University of Washington, Seattle - Professor - principalInvestigator - - - - Sarah - Albertin - - IGE, Univ. Grenoble Alpes, CNRS, INRAE, IRD - Graduate Student - principalInvestigator - - - - Slimane - Bekki - - – LATMOS/IPSL, Sorbonne Université, UVSQ, CNRS - Research Scientist - principalInvestigator - - - - Joël - Savarino - - IGE, Univ. Grenoble Alpes, CNRS, INRAE, IRD - Senior Scientist - principalInvestigator - - - - Konstantin - Gribanov - - Climate and Environment Physics Laboratory, Ural Federal University - Senior Researcher - principalInvestigator - - - - Kerri - Pratt - - Department of Chemistry, University of Michigan, Ann Arbor - Professor - principalInvestigator - - - - Emily - Costa - - Department of Chemistry, University of Michigan, Ann Arbor - Graduate Student - principalInvestigator - - - - Cort - Anastasio - - - Department of Land, Air, & Water Resources, University of California, Davis - Professor - principalInvestigator - - - - Michael - Sunday - - - Department of Land, Air, & Water Resources, University of California, Davis - Postdoctoral researcher - principalInvestigator - - - - Laura - Heinlein - - - Department of Land, Air, & Water Resources, University of California, Davis - Graduate Student - principalInvestigator - - - - Jingqiu - Mao - - Department of Chemistry, Biochemistry, and Geophysical Institute, University of Alaska Fairbanks - Professor - principalInvestigator - - - - Becky - Alexander - - Department of Atmospheric Sciences, University of Washington, Seattle - Professor - principalInvestigator - - - The National Oceanic and Atmospheric Administration (OAR)in the AC4 - Urban atmosphere in a warming climate: chemistry, carbon and composition division. - 0124733 - Fairbanks sulfate isotope measurements during ALPACA - + + NSF-OPP 2024163: Collaborative Research: A New Approach to Firn Evolution using the Taylor Dome Natural Laboratory + NSF-OPP 1417921: Collaborative Research: GreenTrACS: a Greenland Traverse for Accumulation and Climate Studies + NSF-OPP 1417678: Collaborative Research: GreenTrACS: a Greenland Traverse for Accumulation and Climate Studies + Moon_et_al_isotope_data.csv