/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kie.scanner; import org.drools.compiler.kie.builder.impl.InternalKieContainer; import org.drools.compiler.kie.builder.impl.InternalKieModule; import org.drools.compiler.kie.builder.impl.InternalKieScanner; import org.drools.compiler.kie.builder.impl.MemoryKieModule; import org.drools.compiler.kie.builder.impl.ResultsImpl; import org.drools.compiler.kie.builder.impl.ZipKieModule; import org.drools.compiler.kproject.ReleaseIdImpl; import org.drools.compiler.kproject.models.KieModuleModelImpl; import org.drools.compiler.kproject.xml.DependencyFilter; import org.drools.compiler.kproject.xml.PomModel; import org.eclipse.aether.artifact.Artifact; import org.kie.api.KieServices; import org.kie.api.builder.KieModule; import org.kie.api.builder.KieScanner; import org.kie.api.builder.Message; import org.kie.api.builder.ReleaseId; import org.kie.api.builder.Results; import org.kie.api.builder.model.KieModuleModel; import org.kie.api.event.kiescanner.KieScannerEventListener; import org.kie.api.runtime.KieContainer; import org.kie.scanner.event.KieScannerEventSupport; import org.kie.scanner.management.KieScannerMBean; import org.kie.scanner.management.KieScannerMBeanImpl; import org.kie.scanner.management.MBeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static org.drools.compiler.kie.builder.impl.KieBuilderImpl.buildKieModule; import static org.drools.compiler.kie.builder.impl.KieBuilderImpl.setDefaultsforEmptyKieModule; import static org.kie.scanner.ArtifactResolver.getResolverFor; public class KieRepositoryScannerImpl implements InternalKieScanner { private Timer timer; private static final Logger log = LoggerFactory.getLogger(KieScanner.class); private InternalKieContainer kieContainer; private DependencyDescriptor kieProjectDescr; private Map<ReleaseId, DependencyDescriptor> usedDependencies; private ArtifactResolver artifactResolver; private volatile Status status = Status.STARTING; private KieScannerMBean mbean; private long pollingInterval; private KieScannerEventSupport listeners = new KieScannerEventSupport(); @Override public void addListener(KieScannerEventListener listener) { listeners.addEventListener(listener); } @Override public void removeListener(KieScannerEventListener listener) { listeners.removeEventListener(listener); } @Override public Collection<KieScannerEventListener> getListeners() { return listeners.getEventListeners(); } private void changeStatus( Status status ) { this.status = status; listeners.fireKieScannerStatusChangeEventImpl(status); } public synchronized void setKieContainer(KieContainer kieContainer) { if (this.kieContainer != null) { throw new RuntimeException("Cannot change KieContainer on an already initialized KieScanner"); } this.kieContainer = (InternalKieContainer)kieContainer; if (this.kieContainer.getContainerReleaseId() == null) { throw new RuntimeException("The KieContainer's ReleaseId cannot be null. Are you using a KieClasspathContainer?"); } kieProjectDescr = new DependencyDescriptor(this.kieContainer.getReleaseId(), this.kieContainer.getCreationTimestamp()); artifactResolver = getResolverFor(this.kieContainer, true); usedDependencies = indexArtifacts(artifactResolver); KieScannersRegistry.register(this); changeStatus( Status.STOPPED ); if( MBeanUtils.isMBeanEnabled() ) { mbean = new KieScannerMBeanImpl(this); } } private ArtifactResolver getArtifactResolver() { if (artifactResolver == null) { artifactResolver = new ArtifactResolver(); } return artifactResolver; } public synchronized KieModule loadArtifact(ReleaseId releaseId) { return loadArtifact(releaseId, getArtifactResolver()); } public synchronized KieModule loadArtifact(ReleaseId releaseId, InputStream pomXml) { ArtifactResolver resolver = pomXml != null ? ArtifactResolver.getResolverFor(pomXml) : getArtifactResolver(); return loadArtifact( releaseId, resolver ); } public synchronized KieModule loadArtifact(ReleaseId releaseId, PomModel pomModel) { ArtifactResolver resolver = pomModel != null ? ArtifactResolver.getResolverFor(pomModel) : getArtifactResolver(); return loadArtifact( releaseId, resolver ); } private KieModule loadArtifact( ReleaseId releaseId, ArtifactResolver resolver ) { Artifact artifact = resolver.resolveArtifact(releaseId); return artifact != null ? buildArtifact(artifact, resolver) : loadPomArtifact(releaseId); } public synchronized String getArtifactVersion(ReleaseId releaseId) { if (!releaseId.isSnapshot()) { return releaseId.getVersion(); } Artifact artifact = getArtifactResolver().resolveArtifact(releaseId); return artifact != null ? artifact.getVersion() : null; } public synchronized ReleaseId getScannerReleaseId() { return kieContainer.getContainerReleaseId(); } public synchronized ReleaseId getCurrentReleaseId() { return kieContainer.getReleaseId(); } public synchronized Status getStatus() { return status; } private KieModule loadPomArtifact(ReleaseId releaseId) { ArtifactResolver resolver = getResolverFor(releaseId, false); if (resolver == null) { return null; } MemoryKieModule kieModule = new MemoryKieModule(releaseId); addDependencies(kieModule, resolver, resolver.getPomDirectDependencies( DependencyFilter.COMPILE_FILTER )); build(kieModule); return kieModule; } private InternalKieModule buildArtifact(Artifact artifact, ArtifactResolver resolver) { DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(artifact); ReleaseId releaseId = dependencyDescriptor.getReleaseId(); if (releaseId.isSnapshot()) { ((ReleaseIdImpl)releaseId).setSnapshotVersion(artifact.getVersion()); } ZipKieModule kieModule = createZipKieModule(releaseId, artifact.getFile()); if (kieModule != null) { addDependencies(kieModule, resolver, resolver.getArtifactDependecies(dependencyDescriptor.toString())); build(kieModule); } return kieModule; } private void addDependencies(InternalKieModule kieModule, ArtifactResolver resolver, List<DependencyDescriptor> dependencies) { for (DependencyDescriptor dep : dependencies) { InternalKieModule dependency = (InternalKieModule) KieServices.Factory.get().getRepository().getKieModule(dep.getReleaseId()); if (dependency != null) { kieModule.addKieDependency(dependency); } else { Artifact depArtifact = resolver.resolveArtifact(dep.getReleaseId()); if (depArtifact != null && isKJar(depArtifact.getFile())) { ReleaseId depReleaseId = new DependencyDescriptor(depArtifact).getReleaseId(); ZipKieModule zipKieModule = createZipKieModule(depReleaseId, depArtifact.getFile()); if (zipKieModule != null) { kieModule.addKieDependency(zipKieModule); } } } } } private static ZipKieModule createZipKieModule(ReleaseId releaseId, File jar) { KieModuleModel kieModuleModel = getKieModuleModelFromJar(jar); return kieModuleModel != null ? new ZipKieModule(releaseId, kieModuleModel, jar) : null; } private static KieModuleModel getKieModuleModelFromJar(File jar) { ZipFile zipFile = null; try { zipFile = new ZipFile( jar ); ZipEntry zipEntry = zipFile.getEntry(KieModuleModelImpl.KMODULE_JAR_PATH); KieModuleModel kieModuleModel = KieModuleModelImpl.fromXML(zipFile.getInputStream(zipEntry)); setDefaultsforEmptyKieModule(kieModuleModel); return kieModuleModel; } catch ( Exception e ) { } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { } } } return null; } private ResultsImpl build(InternalKieModule kieModule) { ResultsImpl messages = new ResultsImpl(); buildKieModule(kieModule, messages); return messages; } public synchronized void start(long pollingInterval) { if (getStatus() == Status.SHUTDOWN ) { throw new IllegalStateException("The scanner was shut down and can no longer be started."); } if (pollingInterval <= 0) { throw new IllegalArgumentException("pollingInterval must be positive"); } if (timer != null) { throw new IllegalStateException("The scanner is already running"); } startScanTask(pollingInterval); } public synchronized void stop() { if (getStatus() == Status.SHUTDOWN ) { throw new IllegalStateException("The scanner was already shut down."); } if (timer != null) { timer.cancel(); timer = null; } this.pollingInterval = 0; changeStatus( Status.STOPPED ); } public synchronized long getPollingInterval() { return this.pollingInterval; } public void shutdown() { if( getStatus() != Status.SHUTDOWN ) { stop(); // making sure it is stopped changeStatus( Status.SHUTDOWN ); } } private void startScanTask(long pollingInterval) { changeStatus( Status.RUNNING ); this.pollingInterval = pollingInterval; timer = new Timer(true); timer.schedule(new ScanTask(), pollingInterval, pollingInterval); } private class ScanTask extends TimerTask { public void run() { synchronized (KieRepositoryScannerImpl.this) { // don't scan if the scanner was already stopped! This would lead to inconsistent scanner behavior. if (status == Status.STOPPED) { return; } scanNow(); changeStatus( Status.RUNNING ); } } } public synchronized void scanNow() { if (getStatus() == Status.SHUTDOWN ) { throw new IllegalStateException("The scanner was already shut down and can no longer be used."); } // Polling can be started so remember the original state. final Status originalStatus = status; try { changeStatus( Status.SCANNING ); Map<DependencyDescriptor, Artifact> updatedArtifacts = scanForUpdates(); if (updatedArtifacts.isEmpty()) { changeStatus( originalStatus ); return; } changeStatus( Status.UPDATING ); boolean allUpdatesSucceeded = true; // build the dependencies first Map.Entry<DependencyDescriptor, Artifact> containerEntry = null; for (Map.Entry<DependencyDescriptor, Artifact> entry : updatedArtifacts.entrySet()) { if (entry.getKey().isSameArtifact(kieContainer.getContainerReleaseId())) { containerEntry = entry; } else { allUpdatesSucceeded = updateKieModule(entry.getKey(), entry.getValue()) && allUpdatesSucceeded; } } if (containerEntry != null) { allUpdatesSucceeded = updateKieModule(containerEntry.getKey(), containerEntry.getValue()) && allUpdatesSucceeded; } if ( allUpdatesSucceeded ) { log.error("Some errors occured while updating the following artifacts: " + updatedArtifacts); } else { log.info("The following artifacts have been updated: " + updatedArtifacts); } // show we catch exceptions here and shutdown the scanner if one happens? } finally { changeStatus( originalStatus ); } } private boolean updateKieModule(DependencyDescriptor oldDependency, Artifact artifact) { ReleaseId newReleaseId = new DependencyDescriptor(artifact).getReleaseId(); ZipKieModule kieModule = createZipKieModule(newReleaseId, artifact.getFile()); if (kieModule != null) { addDependencies(kieModule, artifactResolver, artifactResolver.getArtifactDependecies(newReleaseId.toString())); ResultsImpl messages = build(kieModule); if ( messages.filterMessages(Message.Level.ERROR).isEmpty()) { Results updateMessages = kieContainer.updateDependencyToVersion(oldDependency.getArtifactReleaseId(), newReleaseId); oldDependency.setArtifactVersion(artifact.getVersion()); messages.getMessages().addAll( updateMessages.getMessages() ); // append all update Results into build Results to notify listeners } listeners.fireKieScannerUpdateResultsEventImpl(messages); return !messages.hasMessages(Message.Level.ERROR); } return false; } private Map<DependencyDescriptor, Artifact> scanForUpdates() { artifactResolver = getResolverFor(kieContainer, true); Map<DependencyDescriptor, Artifact> newArtifacts = new HashMap<DependencyDescriptor, Artifact>(); Artifact newArtifact = artifactResolver.resolveArtifact(this.kieContainer.getContainerReleaseId()); if (newArtifact != null) { DependencyDescriptor resolvedDep = new DependencyDescriptor(newArtifact); if (resolvedDep.isNewerThan(kieProjectDescr)) { newArtifacts.put(kieProjectDescr, newArtifact); kieProjectDescr = new DependencyDescriptor(newArtifact); } } for (DependencyDescriptor dep : artifactResolver.getAllDependecies()) { ReleaseId artifactId = dep.getReleaseIdWithoutVersion(); DependencyDescriptor oldDep = usedDependencies.get(artifactId); if (oldDep != null) { newArtifact = artifactResolver.resolveArtifact(dep.getReleaseId()); if (newArtifact != null) { DependencyDescriptor newDep = new DependencyDescriptor(newArtifact); if (newDep.isNewerThan(oldDep)) { newArtifacts.put(oldDep, newArtifact); usedDependencies.put(artifactId, newDep); } } } } return newArtifacts; } private Map<ReleaseId, DependencyDescriptor> indexArtifacts(ArtifactResolver artifactResolver) { Map<ReleaseId, DependencyDescriptor> depsMap = new HashMap<ReleaseId, DependencyDescriptor>(); for (DependencyDescriptor dep : artifactResolver.getAllDependecies()) { if ( !"test".equals(dep.getScope()) && !"provided".equals(dep.getScope()) && !"system".equals(dep.getScope()) ) { Artifact artifact = artifactResolver.resolveArtifact(dep.getReleaseId()); log.debug( artifact + " resolved to " + artifact.getFile() ); if (isKJar(artifact.getFile())) { depsMap.put(dep.getReleaseIdWithoutVersion(), new DependencyDescriptor(artifact)); } } else { log.debug("{} does not need to be resolved because in scope {}", dep, dep.getScope()); } } return depsMap; } private boolean isKJar(File jar) { try (ZipFile zipFile = new ZipFile( jar )) { ZipEntry zipEntry = zipFile.getEntry( KieModuleModelImpl.KMODULE_JAR_PATH ); return zipEntry != null; } catch (IOException e) { throw new RuntimeException("Failed to open Zip file '" + jar.getAbsolutePath() + "'!", e); } } public synchronized KieScannerMBean getMBean() { return this.mbean; } }