package uk.ac.ebi.fg.myequivalents.dao; import static org.apache.commons.lang3.ArrayUtils.contains; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.util.ArrayList; import javax.persistence.EntityManager; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.io.Charsets; import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import uk.ac.ebi.fg.myequivalents.managers.interfaces.BackupManager; import uk.ac.ebi.fg.myequivalents.managers.interfaces.EntityMappingSearchResult.Bundle; import uk.ac.ebi.fg.myequivalents.managers.interfaces.ExposedService; import uk.ac.ebi.fg.myequivalents.model.Describeable; import uk.ac.ebi.fg.myequivalents.model.Entity; import uk.ac.ebi.fg.myequivalents.model.Repository; import uk.ac.ebi.fg.myequivalents.model.Service; import uk.ac.ebi.fg.myequivalents.model.ServiceCollection; import uk.ac.ebi.fg.myequivalents.utils.jaxb.JAXBUtils; /** * DAO used to implement {@link BackupManager} functionality in the DB back end. * * @author brandizi * <dl><dt>Date:</dt><dd>20 Feb 2015</dd> * */ public class BackupDAO extends AbstractDAO { /** * This is used in {@link BackupDAO#upload(InputStream)}, to create {@link ExposedService}, instead of the * regular {@link Service}. * */ public static class JAXBObjectFactory { public Service createService () { return new ExposedService () {}; } } private RepositoryDAO repoDao; private ServiceCollectionDAO servCollDao; private ServiceDAO serviceDao; private EntityMappingDAO mapDao; private Logger log = LoggerFactory.getLogger ( this.getClass () ); public BackupDAO ( EntityManager entityManager ) { super ( entityManager ); } public int dump ( OutputStream out, Integer offset, Integer limit ) { if ( offset == null ) offset = 0; if ( limit == null ) limit = Integer.MAX_VALUE; int result = 0; // This computations consider that in the first ns items we have repositories, then // service collections, then services and finally the mappings. // long nrepos = repoDao.count (), nsc = -1, ns = -1, nm = -1; if ( nrepos > 0 && offset < nrepos ) result = repoDao.dump ( out, offset, limit == Integer.MAX_VALUE ? null : limit ); if ( result < limit ) { nsc = servCollDao.count (); if ( nsc > 0 && result + nsc < limit ) result += servCollDao.dump ( out, (int) (offset - nrepos), limit == Integer.MAX_VALUE ? null : limit - result ); } if ( result < limit ) { ns = serviceDao.count (); if ( ns > 0 && result + ns < limit ) result += serviceDao.dump ( out, (int) (offset - nrepos - nsc), limit == Integer.MAX_VALUE ? null : limit ); } if ( result < limit ) { nm = mapDao.count (); if ( nm > 0 && result < limit ) result += mapDao.dump ( out, (int) (offset - nrepos - nsc - ns), limit == Integer.MAX_VALUE ? null : limit, 100.0 ); } return result; } /** * As usually, this doesn't take care of opening/committing any transaction, you should use * {@link #postUpload(Describeable, int)}/{@link #postUpload(Bundle, int)} for that. * */ public int upload ( InputStream input ) { try { // We need this because the anonymous handler below won't accept non-final variables and we don't // have time now to move it to an explicit definition. // final int itemCounter[] = { 0 }; SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser saxParser = spf.newSAXParser(); XMLReader xmlReader = saxParser.getXMLReader (); // So the handler intercepts the end of each element type and issue a DB operation when that happens. // xmlReader.setContentHandler ( new DefaultHandler () { private StringBuilder nodeValue = null; @Override public void startElement ( String uri, String localName, String qName, Attributes attrs ) throws SAXException { if ( contains ( new String [] { "service", "service-collection", "repository", "bundle" }, qName ) ) { // mark the node we want to backup nodeValue = new StringBuilder (); } if ( nodeValue == null ) return; // Because we are already inside the node, we have to rebuild its starting tag nodeValue.append ( "<" ).append ( qName ).append ( ' ' ); for ( int i = 0; i < attrs.getLength (); i++ ) nodeValue .append ( attrs.getQName ( i ) ) .append ( " = \"" ).append ( attrs.getValue ( i ) ).append ( "\" " ); nodeValue.append ( ">\n" ); } @Override public void endElement ( String uri, String localName, String qName ) throws SAXException { if ( nodeValue == null ) return; // Collect all the XML you get while parsing the current node nodeValue.append ( "</" ).append ( qName ).append ( ">\n" ); // And now save it, using what you collected so far // if ( "service".equals ( qName ) ) { ExposedService s = JAXBUtils.unmarshal ( new ReaderInputStream ( new StringReader ( nodeValue.toString () ), Charsets.UTF_8 ), ExposedService.class, "com.sun.xml.internal.bind.ObjectFactory", new JAXBObjectFactory () ); // rebuild the repo from its name String repoName = s.getRepositoryName (); if ( StringUtils.trimToNull ( repoName ) != null ) { Repository r = repoDao.findByName ( repoName, false ); if ( r == null ) throw new RuntimeException ( "Error while uploading data from file: repository '" + repoName + "' not found" ); s.setRepository ( r ); } // same for the collection String scName = s.getServiceCollectionName (); if ( StringUtils.trimToNull ( scName ) != null ) { ServiceCollection sc = servCollDao.findByName ( scName, false ); if ( sc == null ) throw new RuntimeException ( "Error while uploading data from file: ServiceCollection '" + scName + "' not found" ); s.setServiceCollection ( sc ); } serviceDao.store ( s.asService () ); postUpload ( s, ++itemCounter [ 0 ] ); nodeValue = null; } else if ( "service-collection".equals ( qName ) ) { ServiceCollection sc = JAXBUtils.unmarshal ( nodeValue.toString (), ServiceCollection.class ); servCollDao.store ( sc ); postUpload ( sc, ++itemCounter [ 0 ] ); nodeValue = null; } else if ( "repository".equals ( qName ) ) { Repository repo = JAXBUtils.unmarshal ( nodeValue.toString (), Repository.class ); repoDao.store ( repo ); postUpload ( repo, ++itemCounter [ 0 ] ); nodeValue = null; } else if ( "bundle".equals ( qName ) ) { Bundle bundle = JAXBUtils.unmarshal ( nodeValue.toString (), Bundle.class ); mapDao.storeMappingBundle ( new ArrayList<Entity> ( bundle.getEntities () ) ); postUpload ( bundle, ++itemCounter [ 0 ] ); nodeValue = null; } if ( nodeValue == null && itemCounter [ 0 ] % 1000 == 0 ) // we just added a new chunk log.info ( "{} items read", itemCounter [ 0 ] ); } }); xmlReader.parse ( new InputSource ( input ) ); return itemCounter [ 0 ]; } catch ( ParserConfigurationException | SAXException | IOException ex ) { throw new RuntimeException ( "Internal error while reading myEquivalents data dump" + ex.getMessage (), ex ); } } public void setEntityManager ( EntityManager entityManager ) { super.setEntityManager ( entityManager ); if ( repoDao == null ) { repoDao = new RepositoryDAO ( entityManager ); servCollDao = new ServiceCollectionDAO ( entityManager ); serviceDao = new ServiceDAO ( entityManager ); mapDao = new EntityMappingDAO ( entityManager ); return; } repoDao.setEntityManager ( entityManager ); servCollDao.setEntityManager ( entityManager ); serviceDao.setEntityManager ( entityManager ); mapDao.setEntityManager ( entityManager ); } /** * An hook to define something to do after having issued an upload operation. This will include * transaction control operations. This default implementation is empty. * */ protected void postUpload ( Describeable d, int itemCounter ) { } /** * @see #postUpload(Describeable, int) */ protected void postUpload ( Bundle b, int itemCounter ) { } public EntityManager getEntityManager () { return entityManager; } }