Package org.guvnor.structure.backend.config

Source Code of org.guvnor.structure.backend.config.ConfigurationServiceImpl$CheckConfigurationUpdates

package org.guvnor.structure.backend.config;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;
import javax.naming.InitialContext;

import org.guvnor.structure.backend.config.watch.AsyncConfigWatchService;
import org.guvnor.structure.backend.config.watch.AsyncWatchServiceCallback;
import org.guvnor.structure.backend.config.watch.ConfigServiceWatchServiceExecutor;
import org.guvnor.structure.backend.config.watch.ConfigServiceWatchServiceExecutorImpl;
import org.guvnor.structure.server.config.ConfigGroup;
import org.guvnor.structure.server.config.ConfigType;
import org.guvnor.structure.server.config.ConfigurationService;
import org.uberfire.commons.async.DescriptiveRunnable;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.IOException;
import org.uberfire.java.nio.base.WatchContext;
import org.uberfire.java.nio.base.options.CommentedOption;
import org.uberfire.java.nio.file.DirectoryStream;
import org.uberfire.java.nio.file.FileSystem;
import org.uberfire.java.nio.file.Files;
import org.uberfire.java.nio.file.Path;
import org.uberfire.java.nio.file.StandardWatchEventKind;
import org.uberfire.java.nio.file.WatchEvent;
import org.uberfire.java.nio.file.WatchKey;
import org.uberfire.java.nio.file.WatchService;
import org.uberfire.security.Identity;

import static org.uberfire.backend.server.util.Paths.*;

@ApplicationScoped
public class ConfigurationServiceImpl implements ConfigurationService,
                                                 AsyncWatchServiceCallback {

    private static final String MONITOR_DISABLED = "org.uberfire.sys.repo.monitor.disabled";
    //    private static final String MONITOR_CHECK_INTERVAL = "org.uberfire.sys.repo.monitor.interval";
    // mainly for windows as *NIX is based on POSIX but escape always to keep it consistent
    private static final String INVALID_FILENAME_CHARS = "[\\,/,:,*,?,\",<,>,|]";

    @Inject
    @Named("system")
    private org.guvnor.structure.repositories.Repository systemRepository;

    @Inject
    private ConfigGroupMarshaller marshaller;

    @Inject
    private Identity identity;

    //Cache of ConfigGroups to avoid reloading them from file
    private final Map<ConfigType, List<ConfigGroup>> configuration = new ConcurrentHashMap<ConfigType, List<ConfigGroup>>();
    private AtomicLong localLastModifiedValue = new AtomicLong( -1 );

    @Inject
    @Named("configIO")
    private IOService ioService;

    // monitor capabilities
    @Inject
    @Repository
    private Event<SystemRepositoryChangedEvent> repoChangedEvent;
    @Inject
    @OrgUnit
    private Event<SystemRepositoryChangedEvent> orgUnitChangedEvent;
    @Inject
    private Event<SystemRepositoryChangedEvent> changedEvent;

    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    private final Set<Future<?>> jobs = new CopyOnWriteArraySet<Future<?>>();

    private ConfigServiceWatchServiceExecutor executor = null;

    private CheckConfigurationUpdates configUpdates = null;

    private WatchService watchService = null;

    @Inject
    @Named("systemFS")
    private FileSystem fs;

    @PostConstruct
    public void setup() {
        Path defaultRoot = fs.getRootDirectories().iterator().next();
        for ( final Path path : fs.getRootDirectories() ) {
            if ( path.toUri().toString().contains( "/master@" ) ) {
                defaultRoot = path;
                break;
            }
        }

        systemRepository.setRoot( convert( defaultRoot ) );

        // enable monitor by default
        if ( System.getProperty( MONITOR_DISABLED ) == null ) {
            watchService = fs.newWatchService();
            configUpdates = new CheckConfigurationUpdates( watchService );
            final ConfigServiceWatchServiceExecutor configServiceWatchServiceExecutor = getWatchServiceExecutor();
            jobs.add( executorService.submit( new DescriptiveRunnable() {
                @Override
                public String getDescription() {
                    return configUpdates.getDescription();
                }

                @Override
                public void run() {
                    configUpdates.execute( configServiceWatchServiceExecutor );
                }
            } ) );
        }
    }

    @PreDestroy
    public void shutdown() {
        if ( configUpdates != null ) {
            configUpdates.deactivate();
        }
        if ( watchService != null ) {
            watchService.close();
        }
        for ( Future<?> job : jobs ) {
            if ( !job.isCancelled() && !job.isDone() ) {
                job.cancel( true );
            }
        }
        executorService.shutdown(); // Disable new tasks from being submitted
        try {
            // Wait a while for existing tasks to terminate
            if ( !executorService.awaitTermination( 60, TimeUnit.SECONDS ) ) {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( !executorService.awaitTermination( 60, TimeUnit.SECONDS ) ) {
                    System.err.println( "Pool did not terminate" );
                }
            }
        } catch ( InterruptedException ie ) {
            // (Re-)Cancel if current thread also interrupted
            executorService.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public List<ConfigGroup> getConfiguration( final ConfigType type ) {
        if ( configuration.containsKey( type ) ) {
            return configuration.get( type );
        }
        final List<ConfigGroup> configGroups = new ArrayList<ConfigGroup>();
        final DirectoryStream<Path> foundConfigs = ioService.newDirectoryStream( ioService.get( systemRepository.getUri() ),
                                                                                 new DirectoryStream.Filter<Path>() {
                                                                                     @Override
                                                                                     public boolean accept( final Path entry ) throws IOException {
                                                                                         if ( !Files.isDirectory( entry ) &&
                                                                                                 !entry.getFileName().toString().startsWith( "." ) &&
                                                                                                 entry.getFileName().toString().endsWith( type.getExt() ) ) {
                                                                                             return true;
                                                                                         }
                                                                                         return false;
                                                                                     }
                                                                                 }
                                                                               );
        //Only load and cache if a file was found!
        final Iterator<Path> it = foundConfigs.iterator();
        if ( it.hasNext() ) {
            while ( it.hasNext() ) {
                final String content = ioService.readAllString( it.next() );
                final ConfigGroup configGroup = marshaller.unmarshall( content );
                configGroups.add( configGroup );
            }
            configuration.put( type, configGroups );
        }
        return configGroups;
    }

    @Override
    public boolean addConfiguration( final ConfigGroup configGroup ) {
        String filename = configGroup.getName().replaceAll( INVALID_FILENAME_CHARS, "_" );

        final Path filePath = ioService.get( systemRepository.getUri() ).resolve( filename + configGroup.getType().getExt() );
        // avoid duplicated writes to not cause cyclic cluster sync
        if ( ioService.exists( filePath ) ) {
            return true;
        }

        final CommentedOption commentedOption = new CommentedOption( getIdentityName(),
                                                                     "Created config " + filePath.getFileName() );
        try {
            ioService.startBatch( new FileSystem[]{filePath.getFileSystem()} );
            ioService.write( filePath, marshaller.marshall( configGroup ), commentedOption );

            updateLastModified();
        } catch ( Exception ex ) {
            throw new RuntimeException( ex );
        } finally {
            ioService.endBatch();
        }
        //Invalidate cache if a new item has been created; otherwise cached value is stale
        configuration.remove( configGroup.getType() );

        return true;
    }

    @Override
    public boolean updateConfiguration( ConfigGroup configGroup ) {
        String filename = configGroup.getName().replaceAll( INVALID_FILENAME_CHARS, "_" );

        final Path filePath = ioService.get( systemRepository.getUri() ).resolve( filename + configGroup.getType().getExt() );

        final CommentedOption commentedOption = new CommentedOption( getIdentityName(),
                                                                     "Updated config " + filePath.getFileName() );
        try {
            ioService.startBatch( new FileSystem[]{filePath.getFileSystem()} );
            ioService.write( filePath, marshaller.marshall( configGroup ), commentedOption );

            updateLastModified();
        } catch ( Exception ex ) {
            throw new RuntimeException( ex );
        } finally {
            ioService.endBatch();
        }
        //Invalidate cache if a new item has been created; otherwise cached value is stale
        configuration.remove( configGroup.getType() );

        return true;
    }

    @Override
    public boolean removeConfiguration( final ConfigGroup configGroup ) {

        //Invalidate cache if an item has been removed; otherwise cached value is stale
        configuration.remove( configGroup.getType() );
        String filename = configGroup.getName().replaceAll( INVALID_FILENAME_CHARS, "_" );
        final Path filePath = ioService.get( systemRepository.getUri() ).resolve( filename + configGroup.getType().getExt() );

        // avoid duplicated writes to not cause cyclic cluster sync
        if ( !ioService.exists( filePath ) ) {
            return true;
        }
        boolean result;
        try {
            ioService.startBatch( new FileSystem[]{filePath.getFileSystem()} );
            result = ioService.deleteIfExists( filePath );
            if ( result ) {
                updateLastModified();
            }
        } catch ( Exception ex ) {
            throw new RuntimeException( ex );
        } finally {
            ioService.endBatch();
        }

        return result;
    }

    protected String getIdentityName() {
        try {
            return identity.getName();
        } catch ( ContextNotActiveException e ) {
            return "unknown";
        }
    }

    protected long getLastModified() {
        final Path lastModifiedPath = ioService.get( systemRepository.getUri() ).resolve( LAST_MODIFIED_MARKER_FILE );

        return ioService.getLastModifiedTime( lastModifiedPath ).toMillis();
    }

    protected void updateLastModified() {
        final Path lastModifiedPath = ioService.get( systemRepository.getUri() ).resolve( LAST_MODIFIED_MARKER_FILE );
        final CommentedOption commentedOption = new CommentedOption( "system", "system repo updated" );

        ioService.write( lastModifiedPath, new Date().toString().getBytes(), commentedOption );

        // update the last value to avoid to be retriggered byt the monitor
        localLastModifiedValue.set( getLastModified() );
    }

    @Override
    public void callback( long value ) {
        localLastModifiedValue.set( value );
        // invalidate cached values as system repo has changed
        configuration.clear();
    }

    private class CheckConfigurationUpdates implements AsyncConfigWatchService {

        private final WatchService ws;
        private boolean active = true;

        public CheckConfigurationUpdates( final WatchService watchService ) {
            this.ws = watchService;
        }

        public void deactivate() {
            this.active = false;
        }

        @Override
        public void execute( final ConfigServiceWatchServiceExecutor wsExecutor ) {
            while ( active ) {
                try {

                    final WatchKey wk;
                    try {
                        wk = ws.take();
                    } catch ( final Exception ex ) {
                        break;
                    }

                    final List<WatchEvent<?>> events = wk.pollEvents();

                    boolean markerFileModified = false;
                    for ( final WatchEvent<?> event : events ) {
                        final WatchContext context = (WatchContext) event.context();
                        if ( event.kind().equals( StandardWatchEventKind.ENTRY_MODIFY ) ) {
                            if ( context.getOldPath().getFileName().toString().equals( LAST_MODIFIED_MARKER_FILE ) ) {
                                markerFileModified = true;
                                break;
                            }
                        } else if ( event.kind().equals( StandardWatchEventKind.ENTRY_CREATE ) ) {
                            if ( context.getPath().getFileName().toString().equals( LAST_MODIFIED_MARKER_FILE ) ) {
                                markerFileModified = true;
                                break;
                            }
                        } else if ( event.kind().equals( StandardWatchEventKind.ENTRY_RENAME ) ) {
                            if ( context.getOldPath().getFileName().toString().equals( LAST_MODIFIED_MARKER_FILE ) ) {
                                markerFileModified = true;
                                break;
                            }
                        } else if ( event.kind().equals( StandardWatchEventKind.ENTRY_DELETE ) ) {
                            if ( context.getOldPath().getFileName().toString().equals( LAST_MODIFIED_MARKER_FILE ) ) {
                                markerFileModified = true;
                                break;
                            }
                        }
                    }

                    if ( markerFileModified ) {
                        wsExecutor.execute( wk, localLastModifiedValue.get(), ConfigurationServiceImpl.this );
                    }

                    boolean valid = wk.reset();
                    if ( !valid ) {
                        break;
                    }
                } catch ( final Exception ignored ) {
                }
            }
        }

        @Override
        public String getDescription() {
            return "Config File Watch Service";
        }
    }

    protected ConfigServiceWatchServiceExecutor getWatchServiceExecutor() {
        if ( executor == null ) {
            ConfigServiceWatchServiceExecutor _executor = null;
            try {
                _executor = InitialContext.doLookup( "java:module/ConfigServiceWatchServiceExecutorImpl" );
            } catch ( final Exception ignored ) {
            }

            if ( _executor == null ) {
                _executor = new ConfigServiceWatchServiceExecutorImpl();
                ( (ConfigServiceWatchServiceExecutorImpl) _executor ).setConfig( systemRepository, ioService, repoChangedEvent, orgUnitChangedEvent, changedEvent );
            }
            executor = _executor;
        }

        return executor;
    }

}
TOP

Related Classes of org.guvnor.structure.backend.config.ConfigurationServiceImpl$CheckConfigurationUpdates

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.