/******************************************************************************* * Copyright (c) 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.repo.importer.job.internal; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.packagedrone.job.ErrorInformation; import org.eclipse.packagedrone.job.JobInstance.Context; import org.eclipse.packagedrone.repo.MetaKey; import org.eclipse.packagedrone.repo.channel.ArtifactInformation; import org.eclipse.packagedrone.repo.channel.ChannelService; import org.eclipse.packagedrone.repo.channel.ModifiableChannel; import org.eclipse.packagedrone.repo.channel.ChannelService.By; import org.eclipse.packagedrone.repo.importer.ImportContext; import org.eclipse.packagedrone.repo.importer.ImportSubContext; import org.eclipse.packagedrone.repo.importer.job.ImporterResult; import org.eclipse.packagedrone.repo.importer.job.ImporterResult.Entry; public abstract class AbstractImportContext implements ImportContext, AutoCloseable { private interface ImportEntry { public void close () throws Exception; public Map<MetaKey, String> getProvidedMetaData (); public String getName (); public InputStream openStream () throws Exception; public List<ImportEntry> getChildren (); } private static abstract class AbstractEntry implements ImportEntry { private final String name; private final Map<MetaKey, String> providedMetaData; private final List<ImportEntry> children = new LinkedList<> (); public AbstractEntry ( final String name, final Map<MetaKey, String> providedMetaData ) { this.name = name; this.providedMetaData = providedMetaData; } @Override public List<ImportEntry> getChildren () { return this.children; } @Override public String getName () { return this.name; } @Override public Map<MetaKey, String> getProvidedMetaData () { return this.providedMetaData; } @Override public void close () throws Exception { closeEntries ( this.children ); } } private static class StreamEntry extends AbstractEntry { private final InputStream stream; public StreamEntry ( final InputStream stream, final String name, final Map<MetaKey, String> providedMetaData ) { super ( name, providedMetaData ); this.stream = stream; } @Override public InputStream openStream () { return this.stream; } @Override public void close () throws Exception { Exception ex = null; try { super.close (); } catch ( final Exception e ) { ex = e; } try { this.stream.close (); } catch ( final Exception e ) { if ( ex != null ) { e.addSuppressed ( ex ); } throw e; } } } private static class FileEntry extends AbstractEntry { private final Path file; private final boolean deleteAfterImport; private BufferedInputStream stream; public FileEntry ( final Path file, final boolean deleteAfterImport, final String name, final Map<MetaKey, String> providedMetaData ) { super ( name, providedMetaData ); this.file = file; this.deleteAfterImport = deleteAfterImport; } @Override public InputStream openStream () throws Exception { this.stream = new BufferedInputStream ( new FileInputStream ( this.file.toFile () ) ); return this.stream; } @Override public void close () throws Exception { Exception ex = null; try { super.close (); } catch ( final Exception e ) { ex = e; } try { if ( this.stream != null ) { try { this.stream.close (); } catch ( final Exception e ) { if ( ex != null ) { e.addSuppressed ( ex ); } throw e; } } } finally { if ( this.deleteAfterImport ) { Files.deleteIfExists ( this.file ); } } } } private final List<ImportEntry> entries = new LinkedList<> (); private final List<CleanupTask> cleanup = new LinkedList<> (); private final Context context; private final ChannelService service; private final String channelId; public AbstractImportContext ( final Context context, final ChannelService service, final String channelId ) { this.context = context; this.service = service; this.channelId = channelId; } @Override public Context getJobContext () { return this.context; } @Override public ImportSubContext scheduleImport ( final InputStream stream, final String name, final Map<MetaKey, String> providedMetaData ) { return scheduleStream ( this.entries, stream, name, providedMetaData ); } @Override public ImportSubContext scheduleImport ( final Path file, final boolean deleteAfterImport, final String name, final Map<MetaKey, String> providedMetaData ) { return scheduleFile ( this.entries, file, deleteAfterImport, name, providedMetaData ); } protected ImportSubContext scheduleFile ( final List<ImportEntry> entries, final Path file, final boolean deleteAfterImport, final String name, final Map<MetaKey, String> providedMetaData ) { final FileEntry entry = new FileEntry ( file, deleteAfterImport, name, providedMetaData ); synchronized ( entries ) { entries.add ( entry ); } return createSubContext ( entry ); } protected ImportSubContext scheduleStream ( final List<ImportEntry> entries, final InputStream stream, final String name, final Map<MetaKey, String> providedMetaData ) { final StreamEntry entry = new StreamEntry ( stream, name, providedMetaData ); synchronized ( entries ) { entries.add ( entry ); } return createSubContext ( entry ); } private ImportSubContext createSubContext ( final AbstractEntry parentEntry ) { return new ImportSubContext () { @Override public ImportSubContext scheduleImport ( final Path file, final boolean deleteAfterImport, final String name, final Map<MetaKey, String> providedMetaData ) { return scheduleFile ( parentEntry.getChildren (), file, deleteAfterImport, name, providedMetaData ); } @Override public ImportSubContext scheduleImport ( final InputStream stream, final String name, final Map<MetaKey, String> providedMetaData ) { return scheduleStream ( parentEntry.getChildren (), stream, name, providedMetaData ); } }; } public ImporterResult process () throws Exception { final ImporterResult result = new ImporterResult (); result.setChannelId ( getChannelId () ); result.setTotalBytes ( processChildren ( result, null, null, this.entries ) ); return result; } private long processChildren ( final ImporterResult result, final ArtifactInformation parent, final Entry parentEntry, final List<ImportEntry> children ) throws Exception { Exception err = null; long bytes = 0; for ( final ImportEntry entry : children ) { if ( err == null ) { try { final ArtifactInformation art; if ( parent == null ) { art = performRootImport ( entry.openStream (), entry.getName (), entry.getProvidedMetaData () ); } else { art = this.service.accessCall ( By.id ( this.channelId ), ModifiableChannel.class, channel -> { return channel.getContext ().createArtifact ( parent.getId (), entry.openStream (), entry.getName (), entry.getProvidedMetaData () ); } ); } bytes += art.getSize (); final Entry newEntry = new Entry ( art.getId (), art.getName (), art.getSize () ); result.getEntries ().add ( newEntry ); bytes += processChildren ( result, art, newEntry, entry.getChildren () ); } catch ( final Exception e ) { err = e; final Entry newEntry = new Entry ( entry.getName (), ErrorInformation.createFrom ( e ) ); result.getEntries ().add ( newEntry ); skipChildren ( newEntry, entry.getChildren () ); } } else { final Entry newEntry = new Entry ( entry.getName () ); result.getEntries ().add ( newEntry ); skipChildren ( newEntry, entry.getChildren () ); } } if ( err != null ) { throw err; } return bytes; } private void skipChildren ( final Entry parentEntry, final List<ImportEntry> children ) { for ( final ImportEntry entry : children ) { parentEntry.getChildren ().add ( new Entry ( entry.getName () ) ); } } protected abstract String getChannelId (); protected abstract ArtifactInformation performRootImport ( InputStream stream, String name, Map<MetaKey, String> providedMetaData ); @Override public void close () throws Exception { final LinkedList<Exception> errors = new LinkedList<> (); try { closeEntries ( this.entries ); } catch ( final Exception e ) { errors.add ( e ); } for ( final CleanupTask task : this.cleanup ) { try { task.cleanup (); } catch ( final Exception e ) { errors.add ( e ); } } final Exception first = errors.pollFirst (); if ( first != null ) { for ( final Exception e : errors ) { first.addSuppressed ( e ); } throw first; } } protected static void closeEntries ( final List<ImportEntry> entries ) throws Exception { final LinkedList<Exception> errors = new LinkedList<> (); // close all for ( final ImportEntry entry : entries ) { try { entry.close (); } catch ( final Exception e ) { errors.add ( e ); } } // throw later if ( !errors.isEmpty () ) { final Exception first = errors.pollFirst (); for ( final Exception ex : errors ) { first.addSuppressed ( ex ); } throw first; } } @Override public void addCleanupTask ( final CleanupTask cleanup ) { this.cleanup.add ( cleanup ); } }