/******************************************************************************* * 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.job.apm; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; import org.eclipse.packagedrone.job.ErrorInformation; import org.eclipse.packagedrone.job.JobFactory; import org.eclipse.packagedrone.job.JobFactoryDescriptor; import org.eclipse.packagedrone.job.JobHandle; import org.eclipse.packagedrone.job.JobInstance; import org.eclipse.packagedrone.job.JobInstance.Context; import org.eclipse.packagedrone.job.JobManager; import org.eclipse.packagedrone.job.JobRequest; import org.eclipse.packagedrone.job.State; import org.eclipse.packagedrone.job.apm.model.JobInstanceEntity; import org.eclipse.packagedrone.job.apm.model.JobModel; import org.eclipse.packagedrone.job.apm.model.JobModelProvider; import org.eclipse.packagedrone.job.apm.model.JobWriteModel; import org.eclipse.packagedrone.repo.MetaKey; import org.eclipse.packagedrone.storage.apm.StorageManager; import org.eclipse.packagedrone.storage.apm.StorageRegistration; import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JobManagerImpl implements JobManager { public class ContextImpl implements Context, Runnable { private final String id; private long totalAmount; private long worked; private String label; private final JobFactory factory; private final String data; public ContextImpl ( final String id, final JobFactory factory, final String data ) { this.id = id; this.factory = factory; this.data = data; } @Override public void run () { try { final JobInstance instance = this.factory.createInstance ( this.data ); internalSetRunning ( this.id ); instance.run ( this ); } catch ( final Throwable e ) { logger.debug ( "Failed to run job", e ); internalSetError ( this.id, e ); } finally { internalSetComplete ( this.id ); } } @Override public void beginWork ( final String label, final long amount ) { this.totalAmount = amount; this.label = label; internalStartWork ( this.id, label ); } @Override public void setCurrentTaskName ( final String name ) { internalSetTaskLabel ( this.id, name == null ? this.label : String.format ( "%s: %s", this.label, name ) ); } @Override public void complete () { internalWorked ( this.id, 1.0 ); } @Override public void worked ( final long amount ) { this.worked = Math.min ( this.worked + amount, this.totalAmount ); internalWorked ( this.id, (double)this.worked / (double)this.totalAmount ); } @Override public void setResult ( final String data ) { logger.debug ( "Setting result for job {}: {}", this.id, data ); internalSetResult ( this.id, data ); } } private final static Logger logger = LoggerFactory.getLogger ( JobManagerImpl.class ); private static final MetaKey MODEL_KEY = new MetaKey ( "scheduler", "jobs" ); private StorageManager storageManager; private StorageRegistration handle; private final JobQueue queue = new JobQueue (); private final FactoryManager factoryManager; public JobManagerImpl () { this.factoryManager = new FactoryManager ( FrameworkUtil.getBundle ( JobManagerImpl.class ).getBundleContext () ); } public void setStorageManager ( final StorageManager storageManager ) { this.storageManager = storageManager; } public void start () { this.factoryManager.start (); this.queue.start (); this.handle = this.storageManager.registerModel ( 10_000, MODEL_KEY, new JobModelProvider () ); } public void stop () { this.queue.stop (); if ( this.handle != null ) { this.handle.unregister (); this.handle = null; } this.factoryManager.stop (); } @Override public JobHandle startJob ( final JobRequest job ) { logger.debug ( "Start job: {}", job ); final String factoryId = job.getFactoryId (); final Optional<JobFactory> factory = getJobFactory ( factoryId ); if ( !factory.isPresent () ) { throw new IllegalArgumentException ( String.format ( "Job factory '%s' is unknown", factoryId ) ); } return internalStartJob ( factory.get (), factoryId, job.getData (), job.getProperties () ); } /** * @deprecated should use {@link JobManagerImpl#startJob(String, String)} * instead */ @Override @Deprecated public JobHandle startJob ( final String factoryId, final Object data ) { logger.debug ( "Start job: {} - {}", factoryId, data ); final Optional<JobFactory> factory = getJobFactory ( factoryId ); if ( !factory.isPresent () ) { throw new IllegalArgumentException ( String.format ( "Job factory '%s' is unknown", factoryId ) ); } return internalStartJob ( factory.get (), factoryId, factory.get ().encodeConfiguration ( data ), null ); } @Override public Collection<? extends JobHandle> getActiveJobs () { return this.storageManager.accessCall ( MODEL_KEY, JobModel.class, jobs -> { return jobs.getJobs ().values ().stream ().filter ( job -> !job.isComplete () ).collect ( Collectors.toList () ); } ); } @Override public JobHandle getJob ( final String id ) { logger.trace ( "Get job: {}", id ); return this.storageManager.accessCall ( MODEL_KEY, JobModel.class, jobs -> jobs.getJobs ().get ( id ) ); } protected Optional<JobFactory> getJobFactory ( final String factoryId ) { return this.factoryManager.getFactory ( factoryId ); } @Override public JobFactoryDescriptor getFactory ( final String factoryId ) { return getJobFactory ( factoryId ).map ( JobFactory::getDescriptor ).orElse ( null ); } private JobHandle internalStartJob ( final JobFactory factory, final String factoryId, final String data, final Map<String, String> properties ) { final String id = makeId (); final JobHandle handle = this.storageManager.modifyCall ( MODEL_KEY, JobWriteModel.class, jobs -> { final JobInstanceEntity ji = new JobInstanceEntity (); ji.setId ( id ); ji.setFactoryId ( factoryId ); ji.setData ( data ); ji.setState ( State.SCHEDULED ); ji.setLabel ( factory.makeLabel ( data ) ); ji.setProperties ( properties != null ? new HashMap<> ( properties ) : Collections.emptyMap () ); StorageManager.executeAfterPersist ( () -> { this.queue.push ( new ContextImpl ( id, factory, data ) ); } ); jobs.addJob ( ji ); return new JobHandleImpl ( ji ); } ); return handle; } private static String makeId () { return UUID.randomUUID ().toString (); } protected void modifyJob ( final String id, final Consumer<JobInstanceEntity> consumer ) { this.storageManager.modifyRun ( MODEL_KEY, JobWriteModel.class, ( jobs ) -> { final JobInstanceEntity ji = jobs.getJobForUpdate ( id ); if ( ji == null ) { throw new IllegalStateException ( String.format ( "Unable to find job '%s'", id ) ); } consumer.accept ( ji ); } ); } protected void internalSetError ( final String id, final Throwable e ) { final ErrorInformation error = ErrorInformation.createFrom ( e ); logger.debug ( "{}: set error: {}", id, error ); modifyJob ( id, job -> job.setErrorInformation ( error ) ); } public void internalSetResult ( final String id, final String data ) { logger.trace ( "{}: set result: {}", id, data ); modifyJob ( id, job -> job.setResult ( data ) ); } public void internalSetRunning ( final String id ) { logger.debug ( "{}: set running", id ); modifyJob ( id, job -> job.setState ( State.RUNNING ) ); } public void internalSetComplete ( final String id ) { logger.debug ( "{}: set complete", id ); modifyJob ( id, job -> job.setState ( State.COMPLETE ) ); } public void internalWorked ( final String id, final double percentComplete ) { logger.trace ( "{}: worked: {}", id, percentComplete ); modifyJob ( id, job -> job.setPercentComplete ( percentComplete ) ); } public void internalStartWork ( final String id, final String label ) { logger.trace ( "{}: set start work: {}", id, label ); modifyJob ( id, job -> { job.setCurrentWorkLabel ( label ); job.setPercentComplete ( 0.0 ); } ); } public void internalSetTaskLabel ( final String id, final String label ) { logger.trace ( "{}: set task label: {}", id, label ); modifyJob ( id, job -> job.setCurrentWorkLabel ( label ) ); } }