blob: e487c92c878c0e43da2016a8eb314ddd63e2dc0d [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.engine.impl.debug;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.engine.RequestInfo;
import org.apache.sling.engine.RequestInfoProvider;
import org.apache.sling.engine.impl.Config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
/**
* Track requests.
*/
@Component(
service = {RequestInfoProvider.class},
immediate = true, // track requests from the start
configurationPid = Config.PID)
public class RequestInfoProviderImpl implements RequestInfoProvider {
private volatile ConcurrentNavigableMap<String, RequestInfo> requests;
private volatile List<Pattern> patterns;
private volatile int maxSize;
private static volatile RequestInfoProviderImpl INSTANCE;
@Activate
public RequestInfoProviderImpl(final Config config) {
update(config);
INSTANCE = this;
}
@Modified
protected void update(final Config config) {
this.maxSize = config.sling_max_record_requests();
if (this.maxSize < 0) {
this.maxSize = 0;
}
this.requests = (this.maxSize > 0) ? new ConcurrentSkipListMap<>() : null;
final List<Pattern> compiledPatterns = new ArrayList<>();
if (config.sling_store_pattern_requests() != null) {
for (final String pattern : config.sling_store_pattern_requests()) {
if (pattern != null && pattern.trim().length() > 0) {
compiledPatterns.add(Pattern.compile(pattern.trim()));
}
}
}
this.patterns = compiledPatterns;
}
@Deactivate
protected void deactivate() {
INSTANCE = null;
this.requests = null;
this.patterns = Collections.emptyList();
}
public static void recordRequest(final SlingHttpServletRequest r) {
final RequestInfoProviderImpl local = INSTANCE;
if (local != null) {
local.addRequest(r);
}
}
private void addRequest(final SlingHttpServletRequest r) {
final ConcurrentNavigableMap<String, RequestInfo> local = requests;
if (local != null && isEnabledFor(r.getPathInfo())) {
final RequestInfo info = new RequestInfoImpl(r);
synchronized (local) {
if (local.size() == this.maxSize) {
local.remove(local.firstKey());
}
local.put(info.getId(), info);
}
}
}
@Override
public boolean isEnabled() {
return this.requests != null;
}
@Override
public boolean isEnabledFor(final String path) {
if (this.requests != null) {
boolean accept = patterns.isEmpty();
for (Pattern pattern : patterns) {
if (pattern.matcher(path).matches()) {
accept = true;
break;
}
}
return accept;
}
return false;
}
@Override
public int getMayNumberOfInfos() {
return this.getMaxNumberOfInfos();
}
@Override
public int getMaxNumberOfInfos() {
return this.maxSize;
}
@Override
public void clear() {
final ConcurrentNavigableMap<String, RequestInfo> local = requests;
if (local != null) {
local.clear();
}
}
@Override
public RequestInfo getRequestInfo(final String id) {
final ConcurrentNavigableMap<String, RequestInfo> local = requests;
if (local != null) {
return local.get(id);
}
return null;
}
@Override
public Iterable<RequestInfo> getRequestInfos() {
final ConcurrentNavigableMap<String, RequestInfo> local = requests;
if (local != null) {
return local.values();
}
return Collections.emptyList();
}
private static class RequestInfoImpl implements RequestInfo {
private static AtomicLong requestCounter = new AtomicLong(0);
private final String id;
private final String method;
private final String path;
private final String userId;
private final String log;
RequestInfoImpl(final SlingHttpServletRequest request) {
this.id = String.valueOf(System.currentTimeMillis())
.concat("-")
.concat(String.valueOf(requestCounter.incrementAndGet()));
this.method = request.getMethod();
this.path = request.getPathInfo() == null ? "" : request.getPathInfo();
this.userId = request.getRemoteUser();
String text;
try (final StringWriter writer = new StringWriter()) {
final PrintWriter pw = new PrintWriter(writer);
request.getRequestProgressTracker().dump(pw);
pw.flush();
text = writer.toString();
} catch (final IOException ioe) {
text = "";
}
this.log = text;
}
@Override
public @NotNull String getId() {
return this.id;
}
@Override
public @NotNull String getMethod() {
return this.method;
}
@Override
public @NotNull String getPath() {
return this.path;
}
@Override
public @Nullable String getUserId() {
return this.userId;
}
@Override
public @NotNull String getLog() {
return this.log;
}
}
}