/******************************************************************************* * Copyright (c) 2014, 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.aspect; import static org.eclipse.packagedrone.repo.aspect.PropertiesHelper.loadUrl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import org.eclipse.packagedrone.repo.ChannelAspectInformation; import org.eclipse.packagedrone.repo.Version; import org.eclipse.packagedrone.repo.aspect.group.Group; import org.eclipse.packagedrone.repo.aspect.group.GroupInformation; import org.eclipse.packagedrone.utils.profiler.Profile; import org.eclipse.packagedrone.utils.profiler.Profile.Handle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; public class ChannelAspectProcessor { private final ServiceTracker<ChannelAspectFactory, FactoryEntry> tracker; private ServiceTracker<Group, GroupInformation> groupTracker; public static class FactoryEntry { private final ChannelAspectInformation information; private final ChannelAspectFactory service; public FactoryEntry ( final ChannelAspectInformation info, final ChannelAspectFactory service ) { this.information = info; this.service = service; } public ChannelAspectInformation getInformation () { return this.information; } public ChannelAspectFactory getService () { return this.service; } } public ChannelAspectProcessor ( final BundleContext context ) { this.tracker = new ServiceTracker<ChannelAspectFactory, FactoryEntry> ( context, ChannelAspectFactory.class, new ServiceTrackerCustomizer<ChannelAspectFactory, FactoryEntry> () { @Override public FactoryEntry addingService ( final ServiceReference<ChannelAspectFactory> reference ) { return makeEntry ( context, reference ); } @Override public void modifiedService ( final ServiceReference<ChannelAspectFactory> reference, final FactoryEntry service ) { } @Override public void removedService ( final ServiceReference<ChannelAspectFactory> reference, final FactoryEntry service ) { context.ungetService ( reference ); // makeEntry got the service } } ); this.tracker.open (); this.groupTracker = new ServiceTracker<Group, GroupInformation> ( context, Group.class, new ServiceTrackerCustomizer<Group, GroupInformation> () { @Override public GroupInformation addingService ( final ServiceReference<Group> reference ) { final Group group = context.getService ( reference ); return group.getInformation (); } @Override public void modifiedService ( final ServiceReference<Group> reference, final GroupInformation service ) { } @Override public void removedService ( final ServiceReference<Group> reference, final GroupInformation service ) { context.ungetService ( reference ); } } ); this.groupTracker.open (); } public void close () { this.tracker.close (); this.groupTracker.close (); } protected Map<String, ChannelAspectFactory> getAllFactories () { final SortedMap<ServiceReference<ChannelAspectFactory>, FactoryEntry> tracked = this.tracker.getTracked (); final Map<String, ChannelAspectFactory> result = new HashMap<> ( tracked.size () ); for ( final Map.Entry<ServiceReference<ChannelAspectFactory>, FactoryEntry> entry : tracked.entrySet () ) { final Object key = entry.getKey ().getProperty ( ChannelAspectFactory.FACTORY_ID ); if ( ! ( key instanceof String ) ) { continue; } result.put ( (String)key, entry.getValue ().getService () ); } return result; } public <T> void process ( final Collection<String> factoryIds, final Function<ChannelAspect, T> getter, final Consumer<T> consumer ) { final Collection<ChannelAspect> aspects = createAspects ( factoryIds ); for ( final ChannelAspect aspect : aspects ) { try ( Handle handle = Profile.start ( "processAspect|" + aspect.getId () ) ) { final T t = getter.apply ( aspect ); if ( t != null ) { consumer.accept ( t ); } } } } public <T> void process ( final Collection<String> factoryIds, final Function<ChannelAspect, T> getter, final BiConsumer<ChannelAspect, T> consumer ) { final Collection<ChannelAspect> aspects = createAspects ( factoryIds ); for ( final ChannelAspect aspect : aspects ) { try ( Handle handle = Profile.start ( "processAspect|" + aspect.getId () ) ) { final T t = getter.apply ( aspect ); if ( t != null ) { consumer.accept ( aspect, t ); } } } } private Collection<ChannelAspect> createAspects ( final Collection<String> factoryIds ) { final List<String> missingAspects = new LinkedList<> (); final Map<String, ChannelAspectFactory> factories = getAllFactories (); final List<ChannelAspect> result = new ArrayList<ChannelAspect> ( factoryIds.size () ); for ( final String id : factoryIds ) { final ChannelAspectFactory factory = factories.get ( id ); if ( factory == null ) { missingAspects.add ( id ); } else { final ChannelAspect aspect = factory.createAspect (); if ( aspect != null ) { result.add ( aspect ); } else { missingAspects.add ( id ); } } } if ( !missingAspects.isEmpty () ) { throw new IllegalStateException ( String.format ( "Missing aspects: %s", missingAspects ) ); } return result; } public Map<String, ChannelAspectInformation> getAspectInformations () { final Map<String, ChannelAspectInformation> result = new HashMap<> (); for ( final FactoryEntry entry : this.tracker.getTracked ().values () ) { final ChannelAspectInformation info = entry.getInformation (); result.put ( info.getFactoryId (), info ); } return result; } /** * This actively scans for available aspects and returns their information * objects * * @param context * the context to use * @return the result map, never returns <code>null</code> */ public static Map<String, ChannelAspectInformation> scanAspectInformations ( final BundleContext context ) { Collection<ServiceReference<ChannelAspectFactory>> refs; try { refs = context.getServiceReferences ( ChannelAspectFactory.class, null ); } catch ( final InvalidSyntaxException e ) { // this should never happen since we don't specific a filter return Collections.emptyMap (); } if ( refs == null ) { return Collections.emptyMap (); } final Map<String, ChannelAspectInformation> result = new HashMap<> ( refs.size () ); for ( final ServiceReference<ChannelAspectFactory> ref : refs ) { final ChannelAspectInformation info = makeInformation ( ref ); result.put ( info.getFactoryId (), info ); } return result; } protected static FactoryEntry makeEntry ( final BundleContext context, final ServiceReference<ChannelAspectFactory> ref ) { final ChannelAspectInformation info = makeInformation ( ref ); if ( info == null ) { return null; } return new FactoryEntry ( info, context.getService ( ref ) ); } public static ChannelAspectInformation makeInformation ( final ServiceReference<ChannelAspectFactory> ref ) { final String factoryId = getString ( ref, ChannelAspectFactory.FACTORY_ID, getString ( ref, Constants.SERVICE_PID, null ) ); if ( factoryId == null ) { return null; } final String label = getString ( ref, ChannelAspectFactory.NAME, null ); final String descUrl = getString ( ref, ChannelAspectFactory.DESCRIPTION_FILE, null ); final String description; if ( descUrl != null ) { description = loadUrl ( ref.getBundle (), descUrl ); } else { description = getString ( ref, ChannelAspectFactory.DESCRIPTION, getString ( ref, Constants.SERVICE_DESCRIPTION, null ) ); } final String groupId = getString ( ref, ChannelAspectFactory.GROUP_ID, null ); final Version version = Version.valueOf ( getString ( ref, ChannelAspectFactory.VERSION, null ) ); final SortedSet<String> requires = makeRequires ( ref ); return new ChannelAspectInformation ( factoryId, label, description, groupId, requires, version ); } private static SortedSet<String> makeRequires ( final ServiceReference<ChannelAspectFactory> ref ) { final Object val = ref.getProperty ( ChannelAspectFactory.REQUIRES ); if ( val instanceof String[] ) { return new TreeSet<> ( Arrays.asList ( (String[])val ) ); } if ( val instanceof String ) { final String s = (String)val; if ( s.isEmpty () ) { return null; } return new TreeSet<> ( Arrays.asList ( s.split ( "[\\p{Space},]+" ) ) ); } return null; } public List<ChannelAspectInformation> resolve ( final Collection<String> aspects ) { final Map<String, ChannelAspectInformation> infos = getAspectInformations (); final List<ChannelAspectInformation> result = new ArrayList<ChannelAspectInformation> ( aspects.size () ); for ( final String aspect : aspects ) { final ChannelAspectInformation ai = infos.get ( aspect ); if ( ai == null ) { result.add ( ChannelAspectInformation.unresolved ( aspect ) ); } else { result.add ( ai ); } } return result; } private static String getString ( final ServiceReference<ChannelAspectFactory> ref, final String name, final String defaultValue ) { final Object v = ref.getProperty ( name ); if ( v == null ) { return defaultValue; } return v.toString (); } public Collection<GroupInformation> getGroups () { return new ArrayList<> ( this.groupTracker.getTracked ().values () ); } }