blob: d0e28f941b7f53501397ed26bd1c4feab148acf9 [file] [log] [blame]
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ 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 ASF 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.xss.impl.status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
/**
* The {@code XSSLibraryStatusService} collects information about the way the XSS Protection API library is used.
*/
@Component(service = XSSStatusService.class)
@Designate(ocd = XSSStatusService.Configuration.class)
public class XSSStatusService {
@ObjectClassDefinition(
name = "Apache Sling XSS Status Service",
description = "The XSS Protection API Status Service provides various statistics about how the library was used."
)
@interface Configuration {
@AttributeDefinition(
name = "Maximum number of recorded invalid URLs",
description = "Once this number is reached, previously recorded invalid URLs will be discarded."
)
int maxNumberOfInvalidUrlsRecorded() default MAX_INVALID_URLS_RECORDED;
}
public static final int MAX_INVALID_URLS_RECORDED = 1000;
private Map<String, AtomicInteger> invalidUrls;
public void reportInvalidUrl(@NotNull String url) {
if (invalidUrls.containsKey(url)) {
invalidUrls.get(url).incrementAndGet();
} else {
invalidUrls.put(url, new AtomicInteger(1));
}
}
public Map<String, AtomicInteger> getInvalidUrls() {
synchronized (invalidUrls) {
return sortByNumericValue(invalidUrls);
}
}
@Activate
private void activate(Configuration configuration) {
invalidUrls = Collections.synchronizedMap(new FixedSizeMap<>(configuration.maxNumberOfInvalidUrlsRecorded()));
}
private static <K, V extends Comparable<? super V>> Map<K, V> sortByComparableValue(Map<K, V> map) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
list.sort(Map.Entry.comparingByValue());
Map<K, V> result = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
private static <K, V extends Number> Map<K, V> sortByNumericValue(Map<K, V> map) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
list.sort((left, right) -> {
double leftNumber = left.getValue().doubleValue();
double rightNumber = right.getValue().doubleValue();
if (leftNumber < rightNumber) {
return -1;
} else if (leftNumber > rightNumber) {
return 1;
}
return 0;
});
Map<K, V> result = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}