package org.springframework.roo.uaa; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Version; import org.osgi.service.component.ComponentContext; import org.springframework.roo.support.osgi.BundleFindingUtils; import org.springframework.uaa.client.TransmissionAwareUaaService; import org.springframework.uaa.client.TransmissionEventListener; import org.springframework.uaa.client.UaaService; import org.springframework.uaa.client.protobuf.UaaClient.FeatureUse; import org.springframework.uaa.client.protobuf.UaaClient.FeatureUse.Builder; import org.springframework.uaa.client.protobuf.UaaClient.Product; /** * Default implementation of {@link UaaRegistrationService}. * * @author Ben Alex * @since 1.1.1 */ @Service @Component public class UaaRegistrationServiceImpl implements UaaRegistrationService, TransmissionEventListener { /** key: bundleSymbolicName, value: customJson */ private final Map<String, String> bsnBuffer = new HashMap<String, String>(); /** key: BSN, value: git commit hash, if available */ private final Map<String, String> bsnCommitHashCache = new HashMap<String, String>(); /** key: BSN, value: version */ private final Map<String, Version> bsnVersionCache = new HashMap<String, Version>(); private BundleContext bundleContext; /** key: projectId, value: list of products */ private final Map<String, List<Product>> projectIdBuffer = new HashMap<String, List<Product>>(); @Reference private PublicFeatureResolver publicFeatureResolver; @Reference private UaaService uaaService; protected void activate(final ComponentContext context) { // Attempt to store the SPRING_ROO Product (via the registerBSN method // so as to be included via possibly-active buffering) bundleContext = context.getBundleContext(); final String bundleSymbolicName = BundleFindingUtils .findFirstBundleForTypeName(context.getBundleContext(), UaaRegistrationServiceImpl.class.getName()); registerBundleSymbolicNameUse(bundleSymbolicName, null); if (uaaService instanceof TransmissionAwareUaaService) { ((TransmissionAwareUaaService) uaaService) .addTransmissionEventListener(this); } } public void afterTransmission(final TransmissionType type, final boolean successful) { } public void beforeTransmission(final TransmissionType type) { if (type == TransmissionType.UPLOAD) { // Good time to flush through to UAA API, so the latest data is // included in the upload flushIfPossible(); } } protected void deactivate(final ComponentContext context) { // Last effort to store the data given we're shutting down flushIfPossible(); if (uaaService instanceof TransmissionAwareUaaService) { ((TransmissionAwareUaaService) uaaService) .removeTransmissionEventListener(this); } } public void flushIfPossible() { if (bsnBuffer.isEmpty() && projectIdBuffer.isEmpty()) { // Nothing to flush return; } if (!uaaService.isUaaTermsOfUseAccepted()) { // We can't flush yet return; } // Flush the features for (final String bundleSymbolicName : bsnBuffer.keySet()) { final String customJson = bsnBuffer.get(bundleSymbolicName); registerBundleSymbolicNameUse(bundleSymbolicName, customJson, false); } bsnBuffer.clear(); // Flush the projects for (final String projectId : projectIdBuffer.keySet()) { for (final Product product : projectIdBuffer.get(projectId)) { registerProject(product, projectId, false); } } projectIdBuffer.clear(); } /** * Populates the version information in the passed {@link Builder}. This * information is obtained by locating the bundle and using its version * metadata. The Git hash code is acquired from the manifest. * <p> * The method returns without error if the bundle could not be found. * * @param featureUseBuilder to insert feature use information into * (required) * @param bundleSymbolicName to locate (required) */ private void populateVersionInfoIfPossible(final Builder featureUseBuilder, final String bundleSymbolicName) { Version version = bsnVersionCache.get(bundleSymbolicName); String commitHash = bsnCommitHashCache.get(bundleSymbolicName); if (version == null) { for (final Bundle b : bundleContext.getBundles()) { if (bundleSymbolicName.equals(b.getSymbolicName())) { version = b.getVersion(); bsnVersionCache.put(bundleSymbolicName, version); final Object manifestResult = b.getHeaders().get( "Git-Commit-Hash"); if (manifestResult != null) { commitHash = manifestResult.toString(); bsnCommitHashCache.put(bundleSymbolicName, commitHash); } break; } } } if (version == null) { // Can't acquire OSGi version information for this bundle, so give // up now return; } featureUseBuilder.setMajorVersion(version.getMajor()); featureUseBuilder.setMinorVersion(version.getMinor()); featureUseBuilder.setPatchVersion(version.getMicro()); featureUseBuilder.setReleaseQualifier(version.getQualifier()); if (commitHash != null && commitHash.length() > 0) { featureUseBuilder.setSourceControlIdentifier(commitHash); } } public void registerBundleSymbolicNameUse(final String bundleSymbolicName, final String customJson) { registerBundleSymbolicNameUse(bundleSymbolicName, customJson, true); } private void registerBundleSymbolicNameUse(final String bundleSymbolicName, String customJson, final boolean flushWhenDone) { Validate.notBlank(bundleSymbolicName, "Bundle symbolic name required"); // Ensure it's a public feature (we do not want to log or buffer private // features) if (!publicFeatureResolver.isPublic(bundleSymbolicName)) { return; } // Turn a null custom JSON into "" for simplicity later, including // buffering if (customJson == null) { customJson = ""; } // If we cannot persist it at present, buffer it for potential // persistence later on if (!uaaService.isUaaTermsOfUseAccepted()) { bsnBuffer.put(bundleSymbolicName, customJson); return; } // Create feature data bytes if possible byte[] featureData = null; if (!"".equals(customJson)) { try { featureData = customJson.getBytes("UTF-8"); } catch (final Exception ignore) { } } // Go and register it final FeatureUse.Builder featureUseBuilder = FeatureUse.newBuilder(); featureUseBuilder.setName(bundleSymbolicName); populateVersionInfoIfPossible(featureUseBuilder, bundleSymbolicName); if (featureData == null) { // Use this UaaService method, as we want to preserve any feature // data we might have presented previously but hasn't yet been // communicated (important since UAA 1.0.1 due to its delayed // uploads) uaaService.registerFeatureUsage(SPRING_ROO, featureUseBuilder.build()); } else { // New feature data is available, so treat this as overwriting any // existing feature data we might have stored previously uaaService.registerFeatureUsage(SPRING_ROO, featureUseBuilder.build(), featureData); } // Try to flush the buffer while we're at it, given persistence seems to // be OK at present if (flushWhenDone) { flushIfPossible(); } } public void registerProject(final Product product, final String projectId) { registerProject(product, projectId, true); } private void registerProject(final Product product, final String projectId, final boolean flushWhenDone) { Validate.notNull(product, "Product required"); Validate.notBlank(projectId, "Project ID required"); // If we cannot persist it at present, buffer it for potential // persistence later on if (!uaaService.isUaaTermsOfUseAccepted()) { List<Product> value = projectIdBuffer.get(projectId); if (value == null) { value = new ArrayList<Product>(); projectIdBuffer.put(projectId, value); } // We don't buffer it if there's an "identical" (by name and // version) product in there already boolean add = true; for (final Product existing : value) { if (existing.getName().equals(product.getName()) && existing.getMajorVersion() == product .getMajorVersion() && existing.getMinorVersion() == product .getMinorVersion() && existing.getPatchVersion() == product .getPatchVersion() && existing.getReleaseQualifier().equals( product.getReleaseQualifier()) && existing.getSourceControlIdentifier().equals( product.getSourceControlIdentifier())) { add = false; break; } } if (add) { value.add(product); } return; } uaaService.registerProductUsage(product, projectId); // Try to flush the buffer while we're at it, given persistence seems to // be OK at present if (flushWhenDone) { flushIfPossible(); } } public void requestTransmission() { if (uaaService instanceof TransmissionAwareUaaService) { final TransmissionAwareUaaService ta = (TransmissionAwareUaaService) uaaService; ta.requestTransmission(); } } }