diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java new file mode 100644 index 00000000..942d8e50 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.config; + +/** + * Contains default bean names that will be used when no "id" is supplied to the beans. + * + * @author Michael Nitschinger + * @author Simon Baslé + */ +public class BeanNames { + + /** + * Refers to the "" bean. + */ + static final String COUCHBASE_CLUSTER = "cluster"; + + /** + * Refers to the "" bean. + */ + static final String COUCHBASE_BUCKET = "bucket"; + + /** + * Refers to the "" bean. + */ + static final String COUCHBASE_TEMPLATE = "couchbaseTemplate"; + + /** + * Refers to the "" bean + */ + static final String TRANSLATION_SERVICE = "translationService"; + +} diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java new file mode 100644 index 00000000..22c2969d --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.config; + +import com.couchbase.client.java.Bucket; +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.couchbase.monitor.ClientInfo; +import org.springframework.data.couchbase.monitor.ClusterInfo; +import org.springframework.util.StringUtils; + +/** + * Enables Parsing of the "" configuration bean. + *

+ * In order to enable JMX, different JmxComponents need to be registered. The dependency to the original + * {@link Bucket} object is solved through the "bucket-ref" attribute. + * + * @author Michael Nitschinger + * @author Simon Baslé + */ +public class CouchbaseJmxParser implements BeanDefinitionParser { + + /** + * Parse the element and dispatch the registration of the JMX components. + * + * @param element the XML element which contains the attributes. + * @param parserContext encapsulates the parsing state and configuration. + * @return null, because no bean instance needs to be returned. + */ + public BeanDefinition parse(final Element element, final ParserContext parserContext) { + String bucketName = element.getAttribute("bucket-ref"); + if (!StringUtils.hasText(bucketName)) { + bucketName = BeanNames.COUCHBASE_BUCKET; + } + registerJmxComponents(bucketName, element, parserContext); + return null; + } + + /** + * Register the JMX components in the context. + * + * @param element the XML element which contains the attributes. + * @param parserContext encapsulates the parsing state and configuration. + * @parma refBucketName the reference name to the couchbase bucket. + */ + protected void registerJmxComponents(final String refBucketName, + final Element element, final ParserContext parserContext) { + Object eleSource = parserContext.extractSource(element); + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); + + createBeanDefEntry(ClientInfo.class, compositeDef, refBucketName, eleSource, parserContext); + createBeanDefEntry(ClusterInfo.class, compositeDef, refBucketName, eleSource, parserContext); + + parserContext.registerComponent(compositeDef); + } + + /** + * Creates Bean Definitions for JMX components and adds them as a nested component. + * + * @param clazz the class type to register. + * @param compositeDef component that can hold nested components. + * @param refName the reference name to the couchbase client. + * @param eleSource source element to reference. + * @param parserContext encapsulates the parsing state and configuration. + */ + protected void createBeanDefEntry(final Class clazz, final CompositeComponentDefinition compositeDef, + final String refName, final Object eleSource, final ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); + builder.getRawBeanDefinition().setSource(eleSource); + builder.addConstructorArgReference(refName); + BeanDefinition assertDef = builder.getBeanDefinition(); + String assertName = parserContext.getReaderContext().registerWithGeneratedName(assertDef); + compositeDef.addNestedComponent(new BeanComponentDefinition(assertDef, assertName)); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java new file mode 100644 index 00000000..6e4d50d6 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.config; + +import org.springframework.beans.factory.xml.NamespaceHandler; +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * {@link NamespaceHandler} for Couchbase configuration. + *

+ * This handler acts as a container for one or more bean parsers and registers them. During parsing, the elements + * get analyzed and the appropriate registered parser is called. + * + * @author Michael Nitschinger + */ +public class CouchbaseNamespaceHandler extends NamespaceHandlerSupport { + + /** + * Register bean definition parsers in the namespace handler. + */ + public final void init() { + //TODO repositories (CouchbaseRepositoryConfigurationExtension and RepositoryBeanDefinitionParser) + //TODO bucket + //TODO cluster + registerBeanDefinitionParser("jmx", new CouchbaseJmxParser()); + registerBeanDefinitionParser("template", new CouchbaseTemplateParser()); + //TODO translation service + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java new file mode 100644 index 00000000..c4b0de93 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.util.StringUtils; + +/** + * Parser for "" bean definitions. + *

+ * The outcome of this bean definition parser will be a constructed {@link CouchbaseTemplate}. + * + * @author Michael Nitschinger + */ +public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser { + + /** + * Resolve the bean ID and assign a default if not set. + * + * @param element the XML element which contains the attributes. + * @param definition the bean definition to work with. + * @param parserContext encapsulates the parsing state and configuration. + * @return the ID to work with. + */ + @Override + protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) { + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_TEMPLATE; + } + + /** + * Defines the bean class that will be constructed. + * + * @param element the XML element which contains the attributes. + * @return the class type to instantiate. + */ + @Override + protected Class getBeanClass(final Element element) { + return CouchbaseTemplate.class; + } + + /** + * Parse the bean definition and build up the bean. + * + * @param element the XML element which contains the attributes. + * @param bean the builder which builds the bean. + */ + @Override + protected void doParse(final Element element, final BeanDefinitionBuilder bean) { + String bucketRef = element.getAttribute("bucket-ref"); + String converterRef = element.getAttribute("converter-ref"); + String translationServiceRef = element.getAttribute("translation-service-ref"); + + bean.addConstructorArgReference(StringUtils.hasText(bucketRef) ? bucketRef : BeanNames.COUCHBASE_BUCKET); + + if (StringUtils.hasText(converterRef)) { + bean.addConstructorArgReference(converterRef); + } + + if (StringUtils.hasText(translationServiceRef)) { + bean.addConstructorArgReference(translationServiceRef); + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java b/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java new file mode 100644 index 00000000..a5bc7eb0 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.monitor; + +import java.net.InetAddress; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.bucket.BucketInfo; + +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedResource; + +/** + * Exposes basic client information. + * + * @author Michael Nitschinger + * @author Simon Baslé + */ +@ManagedResource(description = "Client Information") +public class ClientInfo { + + private final Bucket bucket; + private final BucketInfo info; + + public ClientInfo(final Bucket bucket) { + this.bucket = bucket; + this.info = bucket.bucketManager().info(); + } + + @ManagedAttribute(description = "Hostnames of connected nodes") + public String getHostNames() { + StringBuilder result = new StringBuilder(); + for (InetAddress node : info.nodeList()) { + result.append(node.toString()).append(","); + } + return result.toString(); + } + + @ManagedAttribute(description = "Number of connected nodes") + public int getNumberOfNodes() { + return info.nodeCount(); + } + + //TODO obtain count of available nodes vs unavailable ones and expose it + +} diff --git a/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java b/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java new file mode 100644 index 00000000..094c26e5 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed 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.springframework.data.couchbase.monitor; + +import java.net.InetAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.bucket.BucketInfo; + +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.web.client.RestTemplate; + +/** + * Exposes basic cluster information. + * + * @author Michael Nitschinger + * @author Simon Baslé + */ +@ManagedResource(description = "Cluster Information") +public class ClusterInfo { + + private final RestTemplate template; + private final Bucket bucket; + private final BucketInfo info; + + public ClusterInfo(final Bucket bucket) { + this.template = new RestTemplate(); + this.bucket = bucket; + this.info = bucket.bucketManager().info(); + } + + @ManagedMetric(description = "Total RAM assigned") + public long getTotalRAMAssigned() { + return convertPotentialLong(parseStorageTotals().get("ram").get("total")); + } + + @ManagedMetric(description = "Total RAM used") + public long getTotalRAMUsed() { + return convertPotentialLong(parseStorageTotals().get("ram").get("used")); + } + + @ManagedMetric(description = "Total Disk Space assigned") + public long getTotalDiskAssigned() { + return convertPotentialLong(parseStorageTotals().get("hdd").get("total")); + } + + @ManagedMetric(description = "Total Disk Space used") + public long getTotalDiskUsed() { + return convertPotentialLong(parseStorageTotals().get("hdd").get("used")); + } + + @ManagedMetric(description = "Total Disk Space free") + public long getTotalDiskFree() { + return convertPotentialLong(parseStorageTotals().get("hdd").get("free")); + } + + @ManagedAttribute(description = "Cluster is Balanced") + public boolean getIsBalanced() { + return (Boolean) fetchPoolInfo().get("balanced"); + } + + @ManagedAttribute(description = "Rebalance Status") + public String getRebalanceStatus() { + return (String) fetchPoolInfo().get("rebalanceStatus"); + } + + @ManagedAttribute(description = "Maximum Available Buckets") + public int getMaxBuckets() { + return (Integer) fetchPoolInfo().get("maxBucketCount"); + } + + /** + * Depending on the value size, either int or long can be passed in and get + * converted to long. + * + * @param value the value to convert. + * @return the converted value. + */ + private long convertPotentialLong(Object value) { + if (value instanceof Integer) { + return new Long((Integer) value); + } + else if (value instanceof Long) { + return (Long) value; + } + else { + throw new IllegalStateException("Cannot convert value to long: " + value); + } + } + + protected String randomAvailableHostname() { + List available = info.nodeList(); + Collections.shuffle(available); + return available.get(0).getHostName(); + } + + private HashMap fetchPoolInfo() { + return template.getForObject("http://" + + randomAvailableHostname() + ":8091/pools/default", HashMap.class); + } + + private HashMap parseStorageTotals() { + HashMap stats = fetchPoolInfo(); + return (HashMap) stats.get("storageTotals"); + } + +}