diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java new file mode 100644 index 0000000..9879da4 --- /dev/null +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java @@ -0,0 +1,308 @@ +/* + * 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.segment; + +import static com.google.common.base.Preconditions.checkState; +import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupConfigurationThenFramework; +import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder; +import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean; + +import java.io.File; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.jackrabbit.oak.commons.PropertiesUtil; +import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; +import org.apache.jackrabbit.oak.segment.file.FileStore; +import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder; +import org.apache.jackrabbit.oak.segment.file.FileStoreStatsMBean; +import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider; +import org.apache.jackrabbit.oak.spi.state.ProxyNodeStore; +import org.apache.jackrabbit.oak.spi.whiteboard.Registration; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A factory allowing creation of secondary segment node stores. + *

+ * The different secondaries are distinguished by their role attribute. + */ +@Component(policy = ConfigurationPolicy.REQUIRE, + name="org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory", + configurationFactory=true, + metatype = true, + label = "Apache Jackrabbit Oak Segment-Tar NodeStore Factory", + description = "Factory allowing configuration of adjacent instances of " + + "NodeStore implementation based on Segment model besides a default SegmentNodeStore in same setup." +) +public class SegmentNodeStoreFactory extends ProxyNodeStore + implements SegmentStoreProvider { + + public static final String NAME = "name"; + + @Property( + label = "Role", + description="As multiple SegmentNodeStores can be configured, this parameter defines the role " + + "of 'this' SegmentNodeStore." + ) + public static final String ROLE = "nsProvider.role"; + + @Property( + label = "Directory", + description="Directory location used to store the segment tar files. If not specified then looks " + + "for framework property 'repository.home' otherwise use a subdirectory with name 'tarmk'" + ) + public static final String DIRECTORY = "repository.home"; + + @Property( + label = "Mode", + description="TarMK mode (64 for memory mapping, 32 for normal file access)" + ) + public static final String MODE = "tarmk.mode"; + + @Property( + intValue = 256, + label = "Maximum Tar File Size (MB)", + description = "TarMK maximum file size (MB)" + ) + public static final String SIZE = "tarmk.size"; + + @Property( + intValue = 256, + label = "Cache size (MB)", + description = "Cache size for storing most recently used Segments" + ) + public static final String CACHE = "cache"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private String name; + + private FileStore store; + + private volatile SegmentNodeStore segmentNodeStore; + + private ComponentContext context; + + @Reference + private StatisticsProvider statisticsProvider = StatisticsProvider.NOOP; + + private ServiceRegistration storeRegistration; + private Registration fileStoreStatsMBean; + private WhiteboardExecutor executor; + + @Override + protected SegmentNodeStore getNodeStore() { + checkState(segmentNodeStore != null, "service must be activated when used"); + return segmentNodeStore; + } + + private String getRole() { + String role = PropertiesUtil.toString(property(ROLE), null); + return role; + } + + @Activate + public void activate(ComponentContext context) throws IOException { + this.context = context; + log.info("activate: SegmentNodeStore '"+getRole()+"' starting."); + + registerNodeStore(); + } + + @Deactivate + public void deactivate() { + unregisterNodeStore(); + + synchronized (this) { + segmentNodeStore = null; + + if (store != null) { + store.close(); + store = null; + } + } + } + + private synchronized void registerNodeStore() throws IOException { + if (registerSegmentStore()) { + + if (getRole() != null) { + registerNodeStoreProvider(); + return; + } + + } + } + + private void registerNodeStoreProvider() { + SegmentNodeStore.SegmentNodeStoreBuilder nodeStoreBuilder = SegmentNodeStoreBuilders.builder(store); + segmentNodeStore = nodeStoreBuilder.build(); + Dictionary props = new Hashtable(); + props.put(NodeStoreProvider.ROLE, getRole()); + storeRegistration = context.getBundleContext().registerService(NodeStoreProvider.class.getName(), new NodeStoreProvider() { + @Override + public NodeStore getNodeStore() { + return SegmentNodeStoreFactory.this; + } + }, + props); + log.info("Registered NodeStoreProvider backed by SegmentNodeStore of type '{}'", getRole()); + } + + private boolean registerSegmentStore() throws IOException { + if (context == null) { + log.info("Component still not activated. Ignoring the initialization call"); + return false; + } + + OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext()); + + // Build the FileStore + + FileStoreBuilder builder = fileStoreBuilder(getDirectory()) + .withSegmentCacheSize(getCacheSize()) + .withMaxFileSize(getMaxFileSize()) + .withMemoryMapping(getMode().equals("64")) + .withStatisticsProvider(statisticsProvider); + + try { + store = builder.build(); + } catch (InvalidFileStoreVersionException e) { + log.error("The segment store data is not compatible with the current version. Please use oak-segment-tar or a different version of oak-segment."); + return false; + } + + // Listen for Executor services on the whiteboard + + executor = new WhiteboardExecutor(); + executor.start(whiteboard); + + // Expose statistics about the FileStore + + fileStoreStatsMBean = registerMBean( + whiteboard, + FileStoreStatsMBean.class, + store.getStats(), + FileStoreStatsMBean.TYPE, + "FileStore '" + getRole() + "' statistics" + ); + + return true; + } + + private void unregisterNodeStore() { + if (storeRegistration != null) { + storeRegistration.unregister(); + storeRegistration = null; + } + if (fileStoreStatsMBean != null) { + fileStoreStatsMBean.unregister(); + fileStoreStatsMBean = null; + } + if (executor != null) { + executor.stop(); + executor = null; + } + } + + private File getBaseDirectory() { + String directory = property(DIRECTORY); + + if (directory != null) { + return new File(directory); + } + + return new File("tarmk"); + } + + private File getDirectory() { + return new File(getBaseDirectory(), "segmentstore"); + } + + private String getMode() { + String mode = property(MODE); + + if (mode != null) { + return mode; + } + + return System.getProperty(MODE, System.getProperty("sun.arch.data.model", "32")); + } + + private String getCacheSizeProperty() { + String cache = property(CACHE); + + if (cache != null) { + return cache; + } + + return System.getProperty(CACHE); + } + + private int getCacheSize() { + return Integer.parseInt(getCacheSizeProperty()); + } + + private String getMaxFileSizeProperty() { + String size = property(SIZE); + + if (size != null) { + return size; + } + + return System.getProperty(SIZE, "256"); + } + + private int getMaxFileSize() { + return Integer.parseInt(getMaxFileSizeProperty()); + } + + private String property(String name) { + return lookupConfigurationThenFramework(context, name); + } + + /** + * needed for situations where you have to unwrap the + * SegmentNodeStoreService, to get the SegmentStore, like the failover + */ + @Override + public SegmentStore getSegmentStore() { + return store; + } + + //------------------------------------------------------------< Object >-- + + @Override + public String toString() { + return name + ": " + segmentNodeStore + "[role:" + getRole() + "]"; + } + +} diff --git a/oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStoreFactory.java b/oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStoreFactory.java new file mode 100644 index 0000000..5d9c705 --- /dev/null +++ b/oak-segment/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStoreFactory.java @@ -0,0 +1,307 @@ +/* + * 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.plugins.segment; + +import static com.google.common.base.Preconditions.checkState; +import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupConfigurationThenFramework; +import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean; + +import java.io.File; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.jackrabbit.oak.commons.PropertiesUtil; +import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; +import org.apache.jackrabbit.oak.plugins.segment.file.FileStore; +import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.Builder; +import org.apache.jackrabbit.oak.plugins.segment.file.FileStoreStatsMBean; +import org.apache.jackrabbit.oak.plugins.segment.file.InvalidFileStoreVersionException; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider; +import org.apache.jackrabbit.oak.spi.state.ProxyNodeStore; +import org.apache.jackrabbit.oak.spi.whiteboard.Registration; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A factory allowing creation of secondary segment node stores. + *

+ * The different secondaries are distinguished by their role attribute. + */ +@Component(policy = ConfigurationPolicy.REQUIRE, + name="org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreFactory", + configurationFactory=true, + metatype = true, + label = "Apache Jackrabbit Oak Segment NodeStore Factory", + description = "Factory allowing configuration of adjacent instances of " + + "NodeStore implementation based on Segment model besides a default SegmentNodeStore in same setup." +) +public class SegmentNodeStoreFactory extends ProxyNodeStore + implements SegmentStoreProvider { + + public static final String NAME = "name"; + + @Property( + label = "Role", + description="As multiple SegmentNodeStores can be configured, this parameter defines the role " + + "of 'this' SegmentNodeStore." + ) + public static final String ROLE = "nsProvider.role"; + + @Property( + label = "Directory", + description="Directory location used to store the segment tar files. If not specified then looks " + + "for framework property 'repository.home' otherwise use a subdirectory with name 'tarmk'" + ) + public static final String DIRECTORY = "repository.home"; + + @Property( + label = "Mode", + description="TarMK mode (64 for memory mapping, 32 for normal file access)" + ) + public static final String MODE = "tarmk.mode"; + + @Property( + intValue = 256, + label = "Maximum Tar File Size (MB)", + description = "TarMK maximum file size (MB)" + ) + public static final String SIZE = "tarmk.size"; + + @Property( + intValue = 256, + label = "Cache size (MB)", + description = "Cache size for storing most recently used Segments" + ) + public static final String CACHE = "cache"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private String name; + + private FileStore store; + + private volatile SegmentNodeStore segmentNodeStore; + + private ComponentContext context; + + @Reference + private StatisticsProvider statisticsProvider = StatisticsProvider.NOOP; + + private ServiceRegistration storeRegistration; + private Registration fileStoreStatsMBean; + private WhiteboardExecutor executor; + + @Override + protected SegmentNodeStore getNodeStore() { + checkState(segmentNodeStore != null, "service must be activated when used"); + return segmentNodeStore; + } + + private String getRole() { + String role = PropertiesUtil.toString(property(ROLE), null); + return role; + } + + @Activate + public void activate(ComponentContext context) throws IOException { + this.context = context; + log.info("activate: SegmentNodeStore '"+getRole()+"' starting."); + + registerNodeStore(); + } + + @Deactivate + public void deactivate() { + unregisterNodeStore(); + + synchronized (this) { + segmentNodeStore = null; + + if (store != null) { + store.close(); + store = null; + } + } + } + + private synchronized void registerNodeStore() throws IOException { + if (registerSegmentStore()) { + + if (getRole() != null) { + registerNodeStoreProvider(); + return; + } + + } + } + + private void registerNodeStoreProvider() { + SegmentNodeStore.SegmentNodeStoreBuilder nodeStoreBuilder = SegmentNodeStore.builder(store); + segmentNodeStore = nodeStoreBuilder.build(); + Dictionary props = new Hashtable(); + props.put(NodeStoreProvider.ROLE, getRole()); + storeRegistration = context.getBundleContext().registerService(NodeStoreProvider.class.getName(), new NodeStoreProvider() { + @Override + public NodeStore getNodeStore() { + return SegmentNodeStoreFactory.this; + } + }, + props); + log.info("Registered NodeStoreProvider backed by SegmentNodeStore of type '{}'", getRole()); + } + + private boolean registerSegmentStore() throws IOException { + if (context == null) { + log.info("Component still not activated. Ignoring the initialization call"); + return false; + } + + OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext()); + + // Build the FileStore + + Builder builder = FileStore.builder(getDirectory()) + .withCacheSize(getCacheSize()) + .withMaxFileSize(getMaxFileSize()) + .withMemoryMapping(getMode().equals("64")) + .withStatisticsProvider(statisticsProvider); + + try { + store = builder.build(); + } catch (InvalidFileStoreVersionException e) { + log.error("The segment store data is not compatible with the current version. Please use oak-segment-tar or a different version of oak-segment."); + return false; + } + + // Listen for Executor services on the whiteboard + + executor = new WhiteboardExecutor(); + executor.start(whiteboard); + + // Expose statistics about the FileStore + + fileStoreStatsMBean = registerMBean( + whiteboard, + FileStoreStatsMBean.class, + store.getStats(), + FileStoreStatsMBean.TYPE, + "FileStore '" + getRole() + "' statistics" + ); + + return true; + } + + private void unregisterNodeStore() { + if (storeRegistration != null) { + storeRegistration.unregister(); + storeRegistration = null; + } + if (fileStoreStatsMBean != null) { + fileStoreStatsMBean.unregister(); + fileStoreStatsMBean = null; + } + if (executor != null) { + executor.stop(); + executor = null; + } + } + + private File getBaseDirectory() { + String directory = property(DIRECTORY); + + if (directory != null) { + return new File(directory); + } + + return new File("tarmk"); + } + + private File getDirectory() { + return new File(getBaseDirectory(), "segmentstore"); + } + + private String getMode() { + String mode = property(MODE); + + if (mode != null) { + return mode; + } + + return System.getProperty(MODE, System.getProperty("sun.arch.data.model", "32")); + } + + private String getCacheSizeProperty() { + String cache = property(CACHE); + + if (cache != null) { + return cache; + } + + return System.getProperty(CACHE); + } + + private int getCacheSize() { + return Integer.parseInt(getCacheSizeProperty()); + } + + private String getMaxFileSizeProperty() { + String size = property(SIZE); + + if (size != null) { + return size; + } + + return System.getProperty(SIZE, "256"); + } + + private int getMaxFileSize() { + return Integer.parseInt(getMaxFileSizeProperty()); + } + + private String property(String name) { + return lookupConfigurationThenFramework(context, name); + } + + /** + * needed for situations where you have to unwrap the + * SegmentNodeStoreService, to get the SegmentStore, like the failover + */ + @Override + public SegmentStore getSegmentStore() { + return store; + } + + //------------------------------------------------------------< Object >-- + + @Override + public String toString() { + return name + ": " + segmentNodeStore + "[role:" + getRole() + "]"; + } + +}