diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java index d11c0cc9e71..831f9c1d82e 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java @@ -67,6 +67,11 @@ public class Jcr { public static final int DEFAULT_OBSERVATION_QUEUE_LENGTH = BackgroundObserver.DEFAULT_QUEUE_SIZE; + /* + Default string property size above which a warning will be logged + */ + public static final int DEFAULT_WARN_LOG_STRING_PROPERTY_SIZE = 102400; + private final Oak oak; private final Set repositoryInitializers = newLinkedHashSet(); @@ -94,6 +99,11 @@ public class Jcr { private Clusterable clusterable; + /* + Log a warning if string property added is larger than warnLogStringPropertySize + */ + private int warnLogStringPropertySize = DEFAULT_WARN_LOG_STRING_PROPERTY_SIZE; + public Jcr(Oak oak, boolean initialize) { this.oak = oak; @@ -259,6 +269,13 @@ public Jcr withObservationQueueLength(int observationQueueLength) { return this; } + @NotNull + public Jcr withWarnLogStringPropertySize(int warnLogStringPropertySize) { + ensureRepositoryIsNotCreated(); + this.warnLogStringPropertySize = warnLogStringPropertySize; + return this; + } + @NotNull public Jcr with(@NotNull CommitRateLimiter commitRateLimiter) { ensureRepositoryIsNotCreated(); @@ -387,7 +404,8 @@ public Repository createRepository() { securityProvider, observationQueueLength, commitRateLimiter, - fastQueryResultSize); + fastQueryResultSize, + warnLogStringPropertySize); } return repository; } diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java index f6a93142976..a0d24222987 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java @@ -50,6 +50,7 @@ import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider; import org.apache.jackrabbit.oak.api.jmx.SessionMBean; import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser; +import org.apache.jackrabbit.oak.jcr.Jcr; import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy; import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy.Composite; @@ -126,6 +127,7 @@ public class RepositoryImpl implements JackrabbitRepository { createListeningScheduledExecutorService(); private final StatisticManager statisticManager; + private final int warnLogStringPropertySize; /** * Constructor used for backward compatibility. @@ -138,13 +140,29 @@ public RepositoryImpl(@NotNull ContentRepository contentRepository, this(contentRepository, whiteboard, securityProvider, observationQueueLength, commitRateLimiter, false); } - + public RepositoryImpl(@NotNull ContentRepository contentRepository, @NotNull Whiteboard whiteboard, @NotNull SecurityProvider securityProvider, int observationQueueLength, CommitRateLimiter commitRateLimiter, boolean fastQueryResultSize) { + this(contentRepository, + whiteboard, + securityProvider, + observationQueueLength, + commitRateLimiter, + fastQueryResultSize, + Jcr.DEFAULT_WARN_LOG_STRING_PROPERTY_SIZE); + } + + public RepositoryImpl(@NotNull ContentRepository contentRepository, + @NotNull Whiteboard whiteboard, + @NotNull SecurityProvider securityProvider, + int observationQueueLength, + CommitRateLimiter commitRateLimiter, + boolean fastQueryResultSize, + int warnLogStringPropertySize) { this.contentRepository = checkNotNull(contentRepository); this.whiteboard = checkNotNull(whiteboard); this.securityProvider = checkNotNull(securityProvider); @@ -157,6 +175,7 @@ public RepositoryImpl(@NotNull ContentRepository contentRepository, this.fastQueryResultSize = fastQueryResultSize; this.mountInfoProvider = WhiteboardUtils.getService(whiteboard, MountInfoProvider.class); this.blobAccessProvider = WhiteboardUtils.getService(whiteboard, BlobAccessProvider.class); + this.warnLogStringPropertySize = warnLogStringPropertySize; } //---------------------------------------------------------< Repository >--- @@ -347,7 +366,8 @@ protected SessionContext createSessionContext( Map attributes, SessionDelegate delegate, int observationQueueLength, CommitRateLimiter commitRateLimiter) { return new SessionContext(this, statisticManager, securityProvider, whiteboard, attributes, - delegate, observationQueueLength, commitRateLimiter, mountInfoProvider, blobAccessProvider, fastQueryResultSize); + delegate, observationQueueLength, commitRateLimiter, mountInfoProvider, blobAccessProvider, + fastQueryResultSize, warnLogStringPropertySize); } /** diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java index 58f0b8e8353..4ec75d47a36 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java @@ -1379,6 +1379,12 @@ private Property internalSetProperty( final String oakName = getOakPathOrThrow(checkNotNull(jcrName)); final PropertyState state = createSingleState( oakName, value, Type.fromTag(value.getType(), false)); + if (value != null && value.getType() == PropertyType.STRING + && value.getString().length() >= sessionContext.getWarnLogStringPropertySize()) { + LOG.warn("String property {} having length:{} at path {} is larger than configured" + + " value: {}", jcrName, value.getString().length(), this.getPath(), + sessionContext.getWarnLogStringPropertySize()); + } return perform(new ItemWriteOperation("internalSetProperty") { @Override public void checkPreconditions() throws RepositoryException { @@ -1414,7 +1420,14 @@ private Property internalSetProperty( if (values.length > MV_PROPERTY_WARN_THRESHOLD) { LOG.warn("Large multi valued property [{}/{}] detected ({} values).",dlg.getPath(), jcrName, values.length); } - + for (Value value : values) { + if (value != null && value.getType() == PropertyType.STRING + && value.getString().length() >= sessionContext.getWarnLogStringPropertySize()) { + LOG.warn("String property {} having length:{} at path {} is larger than configured" + + " value: {}", jcrName, value.getString().length(), this.getPath(), + sessionContext.getWarnLogStringPropertySize()); + } + } return perform(new ItemWriteOperation("internalSetProperty") { @Override public void checkPreconditions() throws RepositoryException { diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java index 86e71e5ac33..6fb0d5d24d3 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java @@ -41,6 +41,7 @@ import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type; import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider; +import org.apache.jackrabbit.oak.jcr.Jcr; import org.apache.jackrabbit.oak.jcr.delegate.AccessControlManagerDelegator; import org.apache.jackrabbit.oak.jcr.delegate.JackrabbitAccessControlManagerDelegator; import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate; @@ -92,6 +93,7 @@ public class SessionContext implements NamePathMapper { private final SessionDelegate delegate; private final int observationQueueLength; private final CommitRateLimiter commitRateLimiter; + private final int warnLogStringPropertySize; private MountInfoProvider mountInfoProvider; private final NamePathMapper namePathMapper; @@ -133,6 +135,26 @@ public SessionContext( int observationQueueLength, CommitRateLimiter commitRateLimiter, MountInfoProvider mountInfoProvider, @Nullable BlobAccessProvider blobAccessProvider, boolean fastQueryResultSize) { + this(repository, + statisticManager, + securityProvider, whiteboard, + attributes, delegate, + observationQueueLength, commitRateLimiter, + mountInfoProvider, blobAccessProvider, + fastQueryResultSize, Jcr.DEFAULT_WARN_LOG_STRING_PROPERTY_SIZE); + } + + public int getWarnLogStringPropertySize() { + return warnLogStringPropertySize; + } + + public SessionContext( + @NotNull Repository repository, @NotNull StatisticManager statisticManager, + @NotNull SecurityProvider securityProvider, @NotNull Whiteboard whiteboard, + @NotNull Map attributes, @NotNull final SessionDelegate delegate, + int observationQueueLength, CommitRateLimiter commitRateLimiter, + MountInfoProvider mountInfoProvider, @Nullable BlobAccessProvider blobAccessProvider, + boolean fastQueryResultSize, int warnLogStringPropertySize) { this.repository = checkNotNull(repository); this.statisticManager = statisticManager; this.securityProvider = checkNotNull(securityProvider); @@ -151,6 +173,7 @@ public SessionContext( this.valueFactory = new ValueFactoryImpl( delegate.getRoot(), namePathMapper, this.blobAccessProvider); this.fastQueryResultSize = fastQueryResultSize; + this.warnLogStringPropertySize = warnLogStringPropertySize; } public final Map getAttributes() { diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/WarnLogStringPropertySizeTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/WarnLogStringPropertySizeTest.java new file mode 100644 index 00000000000..6df5ffc5527 --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/WarnLogStringPropertySizeTest.java @@ -0,0 +1,94 @@ +/* + * 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.jackrabbit.oak.jcr; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import javax.jcr.Node; +import javax.jcr.Session; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * {@code WarnLogStringPropertySizeTest} checks if Warn log is bein added on adding + * large string properties + */ +public class WarnLogStringPropertySizeTest extends AbstractRepositoryTest { + private final static String testStringPropertyKey = "testStringPropertyKey"; + private final static String testLargeStringPropertyValue = "sadahdkhfkhfkhskfhskfhshfksdasdadda"; + private final static String testSmallStringPropertyValue = "sada"; + private final String nodeImplLogger = "org.apache.jackrabbit.oak.jcr.session.NodeImpl"; + private final String warnMessage = "String property {} having length:{} at path {} is larger than configured value: {}"; + private final int warnLogStringPropertySize = 20; + + public WarnLogStringPropertySizeTest(NodeStoreFixture fixture) { + super(fixture); + } + + ListAppender listAppender = null; + + @Before + public void loggingAppenderStart() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + listAppender = new ListAppender<>(); + listAppender.start(); + context.getLogger(nodeImplLogger).addAppender(listAppender); + } + + @After + public void loggingAppenderStop() { + listAppender.stop(); + } + + @Test + public void noWarnLogOnAddingSmallStringProperties() throws Exception { + Session s = getAdminSession(); + Node test = s.getRootNode().addNode("testSmall"); + test.setProperty(testStringPropertyKey, testSmallStringPropertyValue); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void warnLogOnAddingLargeStringProperties() throws Exception { + Session s = getAdminSession(); + Node test = s.getRootNode().addNode("testLarge"); + test.setProperty(testStringPropertyKey, testLargeStringPropertyValue); + assertTrue(isWarnMessagePresent(listAppender)); + } + + private boolean isWarnMessagePresent(ListAppender listAppender) { + for (ILoggingEvent loggingEvent : listAppender.list) { + if (loggingEvent.getMessage().contains(warnMessage)) { + return true; + } + } + return false; + } + + @Override + protected Jcr initJcr(Jcr jcr) { + return super.initJcr(jcr).withWarnLogStringPropertySize(warnLogStringPropertySize); + } +}