package aQute.remote.agent; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Formatter; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.ServiceReference; import org.osgi.framework.dto.BundleDTO; import org.osgi.framework.dto.FrameworkDTO; import org.osgi.framework.dto.ServiceReferenceDTO; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.FrameworkWiring; import org.osgi.framework.wiring.dto.BundleRevisionDTO; import org.osgi.resource.Capability; import org.osgi.resource.Requirement; import org.osgi.resource.dto.CapabilityDTO; import org.osgi.resource.dto.RequirementDTO; import aQute.lib.converter.Converter; import aQute.lib.converter.TypeReference; import aQute.libg.shacache.ShaCache; import aQute.libg.shacache.ShaSource; import aQute.remote.api.Agent; import aQute.remote.api.Event; import aQute.remote.api.Event.Type; import aQute.remote.api.Supervisor; import aQute.remote.util.Link; /** * Implementation of the Agent. This implementation implements the Agent * interfaces and communicates with a Supervisor interfaces. */ public class AgentServer implements Agent, Closeable, FrameworkListener { AtomicInteger sequence = new AtomicInteger(1000); // // Constant so we do not have to repeat it // private static final TypeReference<Map<String,String>> MAP_STRING_STRING_T = new TypeReference<Map<String,String>>() {}; private static final long[] EMPTY = new long[0]; // // Known keys in the framework properties since we cannot // iterate over framework properties // @SuppressWarnings("deprecation") static String keys[] = { Constants.FRAMEWORK_BEGINNING_STARTLEVEL, Constants.FRAMEWORK_BOOTDELEGATION, Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_TRUST_REPOSITORIES, Constants.FRAMEWORK_COMMAND_ABSPATH, Constants.FRAMEWORK_EXECPERMISSION, Constants.FRAMEWORK_EXECUTIONENVIRONMENT, Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_LIBRARY_EXTENSIONS, Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION, Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY, Constants.FRAMEWORK_STORAGE, Constants.FRAMEWORK_SYSTEMCAPABILITIES, Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA, Constants.FRAMEWORK_SYSTEMPACKAGES, Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, Constants.FRAMEWORK_UUID, Constants.FRAMEWORK_VENDOR, Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_WINDOWSYSTEM, }; private Supervisor remote; private BundleContext context; private final ShaCache cache; private ShaSource source; private final Map<String,String> installed = new HashMap<String,String>(); volatile boolean quit; private static Map<String,AgentDispatcher> instances = new HashMap<String,AgentDispatcher>(); private Redirector redirector = new NullRedirector(); private Link<Agent,Supervisor> link; private CountDownLatch refresh = new CountDownLatch(0); /** * An agent server is based on a context and takes a name and cache * directory * * @param name the name of the agent's framework * @param context a bundle context of the framework * @param cache the directory for caching */ public AgentServer(String name, BundleContext context, File cache) { this.context = context; if (this.context != null) this.context.addFrameworkListener(this); this.cache = new ShaCache(cache); } /** * Get the framework's DTO */ @Override public FrameworkDTO getFramework() throws Exception { FrameworkDTO fw = new FrameworkDTO(); fw.bundles = getBundles(); fw.properties = getProperties(); fw.services = getServiceReferences(); return fw; } @Override public BundleDTO install(String location, String sha) throws Exception { InputStream in = cache.getStream(sha, source); if (in == null) return null; Bundle b = context.installBundle(location, in); installed.put(b.getLocation(), sha); return toDTO(b); } @Override public BundleDTO installFromURL(String location, String url) throws Exception { InputStream is = new URL(url).openStream(); Bundle b = context.installBundle(location, is); installed.put(b.getLocation(), url); return toDTO(b); } @Override public String start(long... ids) { StringBuilder sb = new StringBuilder(); for (long id : ids) { Bundle bundle = context.getBundle(id); try { bundle.start(); } catch (BundleException e) { sb.append(e.getMessage()).append("\n"); } } return sb.length() == 0 ? null : sb.toString(); } @Override public String stop(long... ids) { StringBuilder sb = new StringBuilder(); for (long id : ids) { Bundle bundle = context.getBundle(id); try { bundle.stop(); } catch (BundleException e) { sb.append(e.getMessage()).append("\n"); } } return sb.length() == 0 ? null : sb.toString(); } @Override public String uninstall(long... ids) { StringBuilder sb = new StringBuilder(); for (long id : ids) { Bundle bundle = context.getBundle(id); try { bundle.uninstall(); installed.remove(bundle.getBundleId()); } catch (BundleException e) { sb.append(e.getMessage()).append("\n"); } } return sb.length() == 0 ? null : sb.toString(); } @Override public String update(Map<String,String> bundles) throws InterruptedException { refresh.await(); Formatter out = new Formatter(); if (bundles == null) { bundles = Collections.emptyMap(); } Set<String> toBeDeleted = new HashSet<String>(installed.keySet()); toBeDeleted.removeAll(bundles.keySet()); LinkedHashSet<String> toBeInstalled = new LinkedHashSet<String>(bundles.keySet()); toBeInstalled.removeAll(installed.keySet()); Map<String,String> changed = new HashMap<String,String>(bundles); changed.values().removeAll(installed.values()); changed.keySet().removeAll(toBeInstalled); Set<String> affected = new HashSet<String>(toBeDeleted); affected.addAll(changed.keySet()); LinkedHashSet<Bundle> toBeStarted = new LinkedHashSet<Bundle>(); for (String location : affected) { Bundle b = getBundle(location); if (b == null) { out.format("Could not location bundle %s to stop it", location); continue; } try { if (isActive(b)) toBeStarted.add(b); b.stop(); } catch (Exception e) { printStack(e); out.format("Trying to stop bundle %s : %s", b, e); } } for (String location : toBeDeleted) { Bundle b = getBundle(location); if (b == null) { out.format("Could not find bundle %s to uninstall it", location); continue; } try { b.uninstall(); installed.remove(location); toBeStarted.remove(b); } catch (Exception e) { printStack(e); out.format("Trying to uninstall %s: %s", location, e); } } for (String location : toBeInstalled) { String sha = bundles.get(location); try { InputStream in = cache.getStream(sha, source); if (in == null) { out.format("Could not find file with sha %s for bundle %s", sha, location); continue; } Bundle b = context.installBundle(location, in); installed.put(location, sha); toBeStarted.add(b); } catch (Exception e) { printStack(e); out.format("Trying to install %s: %s", location, e); } } for (Entry<String,String> e : changed.entrySet()) { String location = e.getKey(); String sha = e.getValue(); try { InputStream in = cache.getStream(sha, source); if (in == null) { out.format("Cannot find file for sha %s to update %s", sha, location); continue; } Bundle bundle = getBundle(location); if (bundle == null) { out.format("No such bundle for location %s while trying to update it", location); continue; } if (bundle.getState() == Bundle.UNINSTALLED) context.installBundle(location, in); else bundle.update(in); } catch (Exception e1) { printStack(e1); out.format("Trying to update %s: %s", location, e); } } for (Bundle b : toBeStarted) { try { b.start(); } catch (Exception e1) { printStack(e1); out.format("Trying to start %s: %s", b, e1); } } String result = out.toString(); out.close(); if (result.length() == 0) { refresh(true); return null; } return result; } public String update(long id, String sha) throws Exception { InputStream in = cache.getStream(sha, source); if (in == null) return null; StringBuilder sb = new StringBuilder(); try { Bundle bundle = context.getBundle(id); bundle.update(in); refresh(true); } catch (Exception e) { sb.append(e.getMessage()).append("\n"); } return sb.length() == 0 ? null : sb.toString(); } public String updateFromURL(long id, String url) throws Exception { StringBuilder sb = new StringBuilder(); InputStream is = new URL(url).openStream(); try { Bundle bundle = context.getBundle(id); bundle.update(is); refresh(true); } catch (Exception e) { sb.append(e.getMessage()).append("\n"); } return sb.length() == 0 ? null : sb.toString(); } private Bundle getBundle(String location) { try { Bundle bundle = context.getBundle(location); return bundle; } catch (Exception e) { printStack(e); } return null; } private boolean isActive(Bundle b) { return b.getState() == Bundle.ACTIVE || b.getState() == Bundle.STARTING; } @Override public boolean redirect(int port) throws Exception { if (redirector != null) { if (redirector.getPort() == port) return false; redirector.close(); redirector = new NullRedirector(); } if (port == Agent.NONE) return true; if (port <= Agent.COMMAND_SESSION) { try { redirector = new GogoRedirector(this, context); } catch (Exception e) { throw new IllegalStateException("Gogo is not present in this framework", e); } return true; } if (port == Agent.CONSOLE) { redirector = new ConsoleRedirector(this); return true; } redirector = new SocketRedirector(this, port); return true; } @Override public boolean stdin(String s) throws Exception { if (redirector != null) { redirector.stdin(s); return true; } return false; } @Override public String shell(String cmd) throws Exception { redirect(Agent.COMMAND_SESSION); stdin(cmd); PrintStream ps = redirector.getOut(); if (ps instanceof RedirectOutput) { RedirectOutput rout = (RedirectOutput) ps; return rout.getLastOutput(); } return null; } public void setSupervisor(Supervisor remote) { setRemote(remote); } private List<ServiceReferenceDTO> getServiceReferences() throws Exception { ServiceReference< ? >[] refs = context.getAllServiceReferences(null, null); if (refs == null) return Collections.emptyList(); ArrayList<ServiceReferenceDTO> list = new ArrayList<ServiceReferenceDTO>(refs.length); for (ServiceReference< ? > r : refs) { ServiceReferenceDTO ref = new ServiceReferenceDTO(); ref.bundle = r.getBundle().getBundleId(); ref.id = (Long) r.getProperty(Constants.SERVICE_ID); ref.properties = getProperties(r); Bundle[] usingBundles = r.getUsingBundles(); if (usingBundles == null) ref.usingBundles = EMPTY; else { ref.usingBundles = new long[usingBundles.length]; for (int i = 0; i < usingBundles.length; i++) { ref.usingBundles[i] = usingBundles[i].getBundleId(); } } list.add(ref); } return list; } private Map<String,Object> getProperties(ServiceReference< ? > ref) { Map<String,Object> map = new HashMap<String,Object>(); for (String key : ref.getPropertyKeys()) map.put(key, ref.getProperty(key)); return map; } @SuppressWarnings({ "unchecked", "rawtypes" }) private Map<String,Object> getProperties() { Map map = new HashMap(); map.putAll(System.getenv()); map.putAll(System.getProperties()); for (String key : keys) { Object value = context.getProperty(key); if (value != null) map.put(key, value); } return map; } private List<BundleDTO> getBundles() { Bundle[] bundles = context.getBundles(); ArrayList<BundleDTO> list = new ArrayList<BundleDTO>(bundles.length); for (Bundle b : bundles) { list.add(toDTO(b)); } return list; } private BundleDTO toDTO(Bundle b) { BundleDTO bd = new BundleDTO(); bd.id = b.getBundleId(); bd.lastModified = b.getLastModified(); bd.state = b.getState(); bd.symbolicName = b.getSymbolicName(); bd.version = b.getVersion() == null ? "0" : b.getVersion().toString(); return bd; } void cleanup(int event) throws Exception { if (quit) return; instances.remove(this); quit = true; update(null); redirect(0); sendEvent(event); link.close(); } @Override public void close() throws IOException { try { cleanup(-2); } catch (Exception e) { throw new IOException(e); } } @Override public void abort() throws Exception { cleanup(-3); } private void sendEvent(int code) { Event e = new Event(); e.type = Event.Type.exit; e.code = code; try { remote.event(e); } catch (Exception e1) { printStack(e1); } } @Override public void frameworkEvent(FrameworkEvent event) { try { Event e = new Event(); e.type = Type.framework; e.code = event.getType(); remote.event(e); } catch (Exception e1) { printStack(e1); } } private void printStack(Exception e1) { try { e1.printStackTrace(redirector.getOut()); } catch (Exception e) { // } } public void setRemote(Supervisor supervisor) { this.remote = supervisor; this.source = new ShaSource() { @Override public boolean isFast() { return false; } @Override public InputStream get(String sha) throws Exception { byte[] data = remote.getFile(sha); if (data == null) return null; return new ByteArrayInputStream(data); } }; } @Override public boolean isEnvoy() { return false; } @Override public Map<String,String> getSystemProperties() throws Exception { return Converter.cnv(MAP_STRING_STRING_T, System.getProperties()); } @Override public boolean createFramework(String name, Collection<String> runpath, Map<String,Object> properties) throws Exception { throw new UnsupportedOperationException("This is an agent, we can't create new frameworks (for now)"); } public Supervisor getSupervisor() { return remote; } public void setLink(Link<Agent,Supervisor> link) { setRemote(link.getRemote()); this.link = link; } public boolean ping() { return true; } public BundleContext getContext() { return context; } public void refresh(boolean async) throws InterruptedException { FrameworkWiring f = context.getBundle(0).adapt(FrameworkWiring.class); if (f != null) { refresh = new CountDownLatch(1); f.refreshBundles(null, new FrameworkListener() { @Override public void frameworkEvent(FrameworkEvent event) { refresh.countDown(); } }); if (async) return; refresh.await(); } } @Override public List<BundleDTO> getBundles(long... bundleId) throws Exception { Bundle[] bundles; if (bundleId.length == 0) { bundles = context.getBundles(); } else { bundles = new Bundle[bundleId.length]; for (int i = 0; i < bundleId.length; i++) { bundles[i] = context.getBundle(bundleId[i]); } } List<BundleDTO> bundleDTOs = new ArrayList<BundleDTO>(bundles.length); for (Bundle b : bundles) { BundleDTO dto = toDTO(b); bundleDTOs.add(dto); } return bundleDTOs; } /** * Return the bundle revisions */ @Override public List<BundleRevisionDTO> getBundleRevisons(long... bundleId) throws Exception { Bundle[] bundles; if (bundleId.length == 0) { bundles = context.getBundles(); } else { bundles = new Bundle[bundleId.length]; for (int i = 0; i < bundleId.length; i++) { bundles[i] = context.getBundle(bundleId[i]); } } List<BundleRevisionDTO> revisions = new ArrayList<BundleRevisionDTO>(bundles.length); for (Bundle b : bundles) { BundleRevision resource = b.adapt(BundleRevision.class); BundleRevisionDTO bwd = toDTO(resource); revisions.add(bwd); } return revisions; } /* * Turn a bundle in a Bundle Revision dto. On a r6 framework we could do * this with adapt but on earlier frameworks we're on our own */ private BundleRevisionDTO toDTO(BundleRevision resource) { BundleRevisionDTO brd = new BundleRevisionDTO(); brd.bundle = resource.getBundle().getBundleId(); brd.id = sequence.getAndIncrement(); brd.symbolicName = resource.getSymbolicName(); brd.type = resource.getTypes(); brd.version = resource.getVersion().toString(); brd.requirements = new ArrayList<RequirementDTO>(); for (Requirement r : resource.getRequirements(null)) { brd.requirements.add(toDTO(brd.id, r)); } brd.capabilities = new ArrayList<CapabilityDTO>(); for (Capability c : resource.getCapabilities(null)) { brd.capabilities.add(toDTO(brd.id, c)); } return brd; } private RequirementDTO toDTO(int resource, Requirement r) { RequirementDTO rd = new RequirementDTO(); rd.id = sequence.getAndIncrement(); rd.resource = resource; rd.namespace = r.getNamespace(); rd.directives = r.getDirectives(); rd.attributes = r.getAttributes(); return rd; } private CapabilityDTO toDTO(int resource, Capability r) { CapabilityDTO rd = new CapabilityDTO(); rd.id = sequence.getAndIncrement(); rd.resource = resource; rd.namespace = r.getNamespace(); rd.directives = r.getDirectives(); rd.attributes = r.getAttributes(); return rd; } }