/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The SF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * 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.apache.sling.hc.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.sling.hc.api.HealthCheck; import org.apache.sling.hc.api.execution.HealthCheckSelector; import org.osgi.annotation.versioning.ProviderType; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.sling.hc.api.execution.HealthCheckSelector.tags; import static org.apache.sling.hc.api.execution.HealthCheckSelector.empty; /** * Select from available {@link HealthCheck} services. * Once this filter object and the returned health check services are no longer * be used {@link #dispose()} should be called, to free the service * references. * * This class is not thread safe and instances shouldn't be used concurrently * from different threads. */ @ProviderType public class HealthCheckFilter { private final Logger log = LoggerFactory.getLogger(getClass()); private final BundleContext bundleContext; public static final String OMIT_PREFIX = "-"; private final Set<ServiceReference> usedReferences = new HashSet<ServiceReference>(); /** * Create a new filter object */ public HealthCheckFilter(final BundleContext bc) { bundleContext = bc; } public List<HealthCheck> getHealthChecks(final HealthCheckSelector selector) { final ServiceReference [] refs = this.getHealthCheckServiceReferences(selector); final List<HealthCheck> result = new ArrayList<HealthCheck>(); if ( refs != null ) { final List<ServiceReference> sortedRefs = Arrays.asList(refs); Collections.sort(sortedRefs); for(final ServiceReference ref : sortedRefs) { final HealthCheck hc = (HealthCheck)bundleContext.getService(ref); log.debug("Selected HealthCheck service {}", hc); if ( hc != null ) { this.usedReferences.add(ref); result.add(hc); } } } return result; } public ServiceReference[] getHealthCheckServiceReferences(final HealthCheckSelector selector) { return getHealthCheckServiceReferences(selector, false); } public ServiceReference[] getHealthCheckServiceReferences(final HealthCheckSelector selector, boolean combineTagsWithOr) { final CharSequence filterBuilder = selector != null ? getServiceFilter(selector, combineTagsWithOr) : getServiceFilter(empty(), combineTagsWithOr); log.debug("OSGi service filter in getHealthCheckServiceReferences(): {}", filterBuilder); try { final String filterString = filterBuilder.length() == 0 ? null : filterBuilder.toString(); bundleContext.createFilter(filterString); // check syntax early final ServiceReference[] refs = bundleContext.getServiceReferences(HealthCheck.class.getName(), filterString); if (refs == null) { log.debug("Found no HealthCheck services with filter [{}]", filterString); return new ServiceReference[0]; } else { log.debug("Found {} HealthCheck services with filter [{}]", refs.length, filterString); } return refs; } catch (final InvalidSyntaxException ise) { // this should not happen, but we fail gracefully log.error("Invalid OSGi filter syntax in '" + filterBuilder + "'", ise); return new ServiceReference[0]; } } /** * Get all health check services with one of the supplied tags. * @return A list of services - might be the empty list if none matches * @deprecated use getHealthChecks() instead */ @Deprecated public List<HealthCheck> getTaggedHealthChecks(final String... tags) { final HealthCheckSelector selector = tags(tags); return getHealthChecks(selector); } /** * Get all service references for health check services with one of the supplied tags. Uses logical "and" to combine tags. * @return An array of service references - might be an empty error if none matches * @deprecated use getHealthCheckServiceReferences() instead */ @Deprecated public ServiceReference[] getTaggedHealthCheckServiceReferences(final String... tags) { return getHealthCheckServiceReferences(tags(tags), false); } /** * Get all service references for health check services with one of the supplied tags. * * @param combineWithOr If true will return all health checks that have at least one of the tags set. * If false will return only health checks that have all given tags assigned. * @param tags the tags to look for * @return An array of service references - might be an empty error if none matches * @deprecated use getHealthCheckServiceReferences() instead */ @Deprecated public ServiceReference[] getTaggedHealthCheckServiceReferences(boolean combineWithOr, final String... tags) { final HealthCheckSelector selector = tags(tags); return getHealthCheckServiceReferences(selector, combineWithOr); } /** * Dispose all used service references */ public void dispose() { for(final ServiceReference ref : this.usedReferences) { this.bundleContext.ungetService(ref); } this.usedReferences.clear(); } CharSequence getServiceFilter(HealthCheckSelector selector, boolean combineTagsWithOr) { // Build service filter final StringBuilder filterBuilder = new StringBuilder(); filterBuilder.append("(&(objectClass=").append(HealthCheck.class.getName()).append(")"); final int prefixLen = HealthCheckFilter.OMIT_PREFIX.length(); final StringBuilder filterBuilderForOrOperator = new StringBuilder(); // or filters final StringBuilder tagsBuilder = new StringBuilder(); int tagsAndClauses = 0; if (selector.tags() != null) { for (String tag : selector.tags()) { tag = tag.trim(); if (tag.length() == 0) { continue; } if (tag.startsWith(HealthCheckFilter.OMIT_PREFIX)) { // ommit tags always have to be added as and-clause filterBuilder.append("(!(").append(HealthCheck.TAGS).append("=").append(tag.substring(prefixLen)).append("))"); } else { // add regular tags in the list either to outer and-clause or inner or-clause if (combineTagsWithOr) { filterBuilderForOrOperator.append("(").append(HealthCheck.TAGS).append("=").append(tag).append(")"); } else { tagsBuilder.append("(").append(HealthCheck.TAGS).append("=").append(tag).append(")"); tagsAndClauses++; } } } } boolean addedNameToOrBuilder = false; if (selector.names() != null) { for (String name : selector.names()) { name = name.trim(); if (name.length() == 0) { continue; } if (name.startsWith(HealthCheckFilter.OMIT_PREFIX)) { // ommit tags always have to be added as and-clause filterBuilder.append("(!(").append(HealthCheck.NAME).append("=").append(name.substring(prefixLen)).append("))"); } else { // names are always ORd filterBuilderForOrOperator.append("(").append(HealthCheck.NAME).append("=").append(name).append(")"); addedNameToOrBuilder = true; } } } if (addedNameToOrBuilder) { if (tagsAndClauses > 1) { filterBuilderForOrOperator.append("(&").append(tagsBuilder).append(")"); } else { filterBuilderForOrOperator.append(tagsBuilder); } } else { filterBuilder.append(tagsBuilder); } // add "or" clause if we have accumulated any if (filterBuilderForOrOperator.length() > 0) { filterBuilder.append("(|").append(filterBuilderForOrOperator).append(")"); } filterBuilder.append(")"); return filterBuilder; } }