/******************************************************************************* * 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.channel.apm; import static org.eclipse.packagedrone.repo.channel.apm.ChannelModelProvider.makeBasePath; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Collections; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import org.eclipse.packagedrone.repo.MetaKey; import org.eclipse.packagedrone.repo.channel.ChannelDetails; import org.eclipse.packagedrone.repo.channel.IdTransformer; import org.eclipse.packagedrone.repo.channel.provider.Channel; import org.eclipse.packagedrone.repo.channel.provider.ChannelProvider; import org.eclipse.packagedrone.repo.channel.provider.ProviderInformation; import org.eclipse.packagedrone.storage.apm.StorageManager; import org.eclipse.packagedrone.utils.profiler.Profile; import org.eclipse.packagedrone.utils.profiler.Profile.Handle; import org.eclipse.scada.utils.io.RecursiveDeleteVisitor; import org.osgi.service.event.EventAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An APM based channel provider * <p> * <em>Note: </em> the channel provider implementation is not thread safe. It * must be guaranteed by the caller (normally the ChannelService) that calls to * the channel provider implementation are mutually exclusive. * </p> */ public class ChannelProviderImpl implements ChannelProvider { private final static Logger logger = LoggerFactory.getLogger ( ChannelProviderImpl.class ); private static final ProviderInformation INFO = new ProviderInformation ( "apm", "APM storage", "APM based channel storage" ); private StorageManager manager; private EventAdmin eventAdmin; private final CopyOnWriteArraySet<Listener> listeners = new CopyOnWriteArraySet<> (); private final CopyOnWriteArraySet<ChannelImpl> channels = new CopyOnWriteArraySet<> (); public ChannelProviderImpl () { } public void setManager ( final StorageManager manager ) { this.manager = manager; } public void setEventAdmin ( final EventAdmin eventAdmin ) { this.eventAdmin = eventAdmin; } public void start () { final Path base = this.manager.getContext ().getBasePath ().resolve ( "channels" ); try { Files.list ( base ).forEach ( child -> { if ( !Files.isDirectory ( child ) ) { return; } try { final UUID id = UUID.fromString ( child.getName ( child.getNameCount () - 1 ).toString () ); discoveredChannel ( id.toString () ); } catch ( final IllegalArgumentException e ) { // invalid format logger.info ( String.format ( "Failed to use '%s' as a channel directory", child ), e ); } } ); } catch ( final NoSuchFileException e ) { // ignore the non-existence of the directory } catch ( final IOException e ) { logger.warn ( "Failed to scan for channels", e ); } } public void stop () { fireChange ( null, new CopyOnWriteArraySet<> ( this.channels ) ); for ( final ChannelImpl channel : this.channels ) { channel.dispose (); } this.channels.clear (); } @Override public void addListener ( final Listener listener ) { if ( this.listeners.add ( listener ) ) { // send known fireChange ( new CopyOnWriteArraySet<> ( this.channels ), null ); } } @Override public void removeListener ( final Listener listener ) { this.listeners.remove ( listener ); } protected void fireChange ( final Collection<? extends Channel> added, final Collection<? extends Channel> removed ) { this.listeners.forEach ( listener -> listener.update ( added, removed ) ); } @Override public Channel create ( final ChannelDetails details, final IdTransformer idTransformer ) { final ChannelImpl channel = createNewChannel ( details, idTransformer ); registerChannel ( channel ); return channel; } private void discoveredChannel ( final String channelId ) { try ( Handle h = Profile.start ( this, "discoveredChannel" ) ) { final MetaKey key = new MetaKey ( "channel", channelId ); final ChannelImpl channel = new ChannelImpl ( channelId, this.eventAdmin, key, this.manager, this ); registerChannel ( channel ); } } protected ChannelImpl createNewChannel ( final ChannelDetails details, final IdTransformer idTransformer ) { final String id = UUID.randomUUID ().toString (); final MetaKey key = new MetaKey ( "channel", id ); final ChannelImpl channel = new ChannelImpl ( id, this.eventAdmin, key, this.manager, this ); // we always call modify, in order to persist the channel at least once channel.modifyRun ( model -> { if ( details != null ) { model.setDetails ( details ); } } , idTransformer ); return channel; } private void registerChannel ( final ChannelImpl channel ) { this.channels.add ( channel ); fireChange ( Collections.singleton ( channel ), null ); } public void deleteChannel ( final ChannelImpl channel ) { if ( this.channels.remove ( channel ) ) { final String id = channel.getId (); fireChange ( null, Collections.singleton ( channel ) ); channel.dispose (); deleteChannelContent ( id ); } } private void deleteChannelContent ( final String id ) { Path path = makeBasePath ( this.manager.getContext (), id ); final Path dir = path.getParent (); final Path fn = path.getFileSystem ().getPath ( "x-" + id ); final Path target = dir == null ? fn : dir.resolve ( fn ); try { Files.move ( path, target, StandardCopyOption.ATOMIC_MOVE ); path = target; } catch ( final IOException e ) { logger.warn ( "Failed to rename the channel directoy first", e ); } try { Files.walkFileTree ( path, new RecursiveDeleteVisitor () ); } catch ( final NoSuchFileException e ) { // ignore } catch ( final IOException e ) { throw new RuntimeException ( "Failed to delete channel content", e ); } } @Override public void wipe () { final CopyOnWriteArrayList<ChannelImpl> deletedChannels = new CopyOnWriteArrayList<> ( this.channels ); this.channels.clear (); for ( final Channel channel : deletedChannels ) { try { ( (ChannelImpl)channel ).dispose (); deleteChannelContent ( channel.getId () ); } catch ( final Exception e ) { logger.warn ( "Failed to wipe/delete channel: " + channel.getId (), e ); } } fireChange ( null, deletedChannels ); } @Override public ProviderInformation getInformation () { return INFO; } }