/* * JBoss, Home of Professional Open Source * Copyright 2015, Red Hat, Inc., and individual contributors as indicated * by the @authors tag. * * 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.jboss.as.controller.services.path; import static org.jboss.as.controller.client.helpers.MeasurementUnit.BITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.BYTES; import static org.jboss.as.controller.client.helpers.MeasurementUnit.GIGABITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.GIGABYTES; import static org.jboss.as.controller.client.helpers.MeasurementUnit.KILOBITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.KILOBYTES; import static org.jboss.as.controller.client.helpers.MeasurementUnit.MEGABITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.MEGABYTES; import static org.jboss.as.controller.client.helpers.MeasurementUnit.PETABITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.PETABYTES; import static org.jboss.as.controller.client.helpers.MeasurementUnit.TERABITS; import static org.jboss.as.controller.client.helpers.MeasurementUnit.TERABYTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNIT; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.jboss.as.controller.AbstractRuntimeOnlyHandler; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.ObjectTypeAttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationDefinition; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.client.helpers.MeasurementUnit; import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; import org.jboss.as.controller.descriptions.common.ControllerResolver; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.wildfly.security.manager.WildFlySecurityManager; /** * Handler for file usage metric which contains the total size of a folder and the usable space (as in Java nio). * * @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2015 Red Hat, inc. * * @see java.nio.file.FileStore#getUsableSpace() */ public class PathInfoHandler extends AbstractRuntimeOnlyHandler { private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendInstant().appendZoneId().toFormatter(Locale.ENGLISH); private static final ZoneId ZONE_ID = ZoneId.of(Calendar.getInstance().getTimeZone().getID()); private static final String USED_SPACE = "used-space"; private static final String AVAILABLE_SPACE = "available-space"; private static final String RESOLVED_PATH = "resolved-path"; private static final String LAST_MODIFIED = "last-modified"; private static final String CREATION_TIME = "creation-time"; private static final String OPERATION_NAME = "path-info"; private static final String FILESYSTEM = "filesystem"; private static final AttributeDefinition UNIT_ATTRIBUTE = SimpleAttributeDefinitionBuilder .create(UNIT, ModelType.STRING, true) .setAllowedValues(BYTES.getName(), KILOBYTES.getName(), MEGABYTES.getName(), GIGABYTES.getName(), TERABYTES.getName(), PETABYTES.getName(), BITS.getName(), KILOBITS.getName(), MEGABITS.getName(), GIGABITS.getName(), TERABITS.getName(), PETABITS.getName()) .setDefaultValue(new ModelNode(MeasurementUnit.BYTES.getName())) .build(); private final List<RelativePathSizeAttribute> relativePathAttributes; private final PathManager pathManager; private final AttributeDefinition parentAttribute; private PathInfoHandler(final PathManager pathManager, final AttributeDefinition parentAttribute, final List<RelativePathSizeAttribute> relativePathAttributes) { this.relativePathAttributes = relativePathAttributes; this.parentAttribute = parentAttribute; this.pathManager = pathManager; } /** * Compute the file usage metric which contains the total size of a folder and the usable space (as in Java nio). * @throws org.jboss.as.controller.OperationFailedException * @see java.nio.file.FileStore#getUsableSpace() */ @Override protected void executeRuntimeStep(OperationContext context, ModelNode operation) throws OperationFailedException { ModelNode unitModelNode = UNIT_ATTRIBUTE.resolveModelAttribute(context, operation); MeasurementUnit sizeUnit = MeasurementUnit.BYTES; if (unitModelNode.isDefined()) { try { sizeUnit = MeasurementUnit.valueOf(unitModelNode.asString()); } catch (IllegalArgumentException ex) { } } // Get the resource final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS); final ModelNode model = resource.getModel(); final ModelNode metric = new ModelNode(); for(RelativePathSizeAttribute relativePathAttribute : relativePathAttributes) { String replyParameterName = relativePathAttribute.pathAttribute.getName(); final ModelNode relativeTo; final ModelNode path; try { // Resolve the model values relativeTo = readAttributeValue(context, relativePathAttribute.relativeToAttribute); path = readAttributeValue(context, relativePathAttribute.pathAttribute); } catch (OperationFailedException ex) { return; } // Resolve paths final String result; if (relativeTo.isDefined()) { // If resolving the full path and a path is defined if (path.isDefined()) { result = pathManager.resolveRelativePathEntry(path.asString(), relativeTo.asString()); } else { result = pathManager.getPathEntry(relativeTo.asString()).resolvePath(); } } else if (path.isDefined()) { if (pathManager != null) { result = pathManager.resolveRelativePathEntry(path.asString(), null); } else { result = path.asString(); } } else { throw ControllerLogger.ROOT_LOGGER.noPathToResolve(relativePathAttribute.relativeToAttribute.getName(), replyParameterName, model); } Double offset = MeasurementUnit.calculateOffset(MeasurementUnit.BYTES, sizeUnit); try { Path folder = new File(result).toPath(); PathSizeWalker walker = new PathSizeWalker(); Files.walkFileTree(folder, walker); ModelNode replyParameterNode; if (this.parentAttribute != null) { replyParameterNode = metric.get(parentAttribute.getName()).get(replyParameterName); } else { replyParameterNode = metric.get(replyParameterName); } replyParameterNode.get(USED_SPACE).set(new ModelNode(offset * walker.getSize().doubleValue())); if (Files.exists(folder)) { BasicFileAttributes attributes = Files.getFileAttributeView(folder, BasicFileAttributeView.class).readAttributes(); replyParameterNode.get(RESOLVED_PATH).set(folder.toAbsolutePath().toString()); replyParameterNode.get(CREATION_TIME).set(DATE_FORMAT.format(attributes.creationTime().toInstant().atZone(ZONE_ID))); replyParameterNode.get(LAST_MODIFIED).set(DATE_FORMAT.format(attributes.lastModifiedTime().toInstant().atZone(ZONE_ID))); replyParameterNode.get(AVAILABLE_SPACE).set(new ModelNode(offset * Files.getFileStore(folder).getUsableSpace())); } } catch (IOException ex) { throw new OperationFailedException(ex); } } context.getResult().set(metric); } private ModelNode readAttributeValue(OperationContext context, AttributeDefinition attribute) throws OperationFailedException { final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS); ModelNode model = resource.getModel(); if(this.parentAttribute != null && !this.parentAttribute.equals(attribute)) { model = readAttributeValue(context, this.parentAttribute); } final String attributeName = attribute.getName(); if(model.hasDefined(attributeName)) { return attribute.resolveModelAttribute(context, model); } AttributeAccess access = context.getResourceRegistration().getAttributeAccess(PathAddress.EMPTY_ADDRESS, attributeName); if(access == null) { return new ModelNode(); } OperationStepHandler handler = access.getReadHandler(); ModelNode path; if(handler != null) { ClassLoader oldTccl = WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(handler.getClass()); try { handler.execute(context, Util.getReadAttributeOperation(context.getCurrentAddress(), attributeName)); } finally { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(oldTccl); } path = context.getResult().clone(); context.getResult().setEmptyObject(); } else { path = new ModelNode(); } return path; } private class PathSizeWalker implements FileVisitor<Path> { private final AtomicLong size; private PathSizeWalker() { size = new AtomicLong(0); } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { size.addAndGet(attrs.size()); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } /** * Return the size of the Path walked in bytes. * @return the size of the Path walked in bytes. */ public AtomicLong getSize() { return size; } } public static void registerOperation(final ManagementResourceRegistration resourceRegistration, final PathInfoHandler handler) { registerOperation(resourceRegistration, MeasurementUnit.BYTES, handler); } private static void registerOperation(final ManagementResourceRegistration resourceRegistration, final MeasurementUnit unit, final PathInfoHandler handler) { List<AttributeDefinition> replyParameters = handler.relativePathAttributes.stream().map( att -> { return ObjectTypeAttributeDefinition.Builder.of(att.pathAttribute.getName(), SimpleAttributeDefinitionBuilder.create(USED_SPACE, ModelType.DOUBLE, false) .setUndefinedMetricValue(new ModelNode(0d)) .setMeasurementUnit(unit) .setStorageRuntime() .build(), SimpleAttributeDefinitionBuilder.create(CREATION_TIME, ModelType.STRING, false) .setStorageRuntime() .build(), SimpleAttributeDefinitionBuilder.create(LAST_MODIFIED, ModelType.STRING, false) .setStorageRuntime() .build(), SimpleAttributeDefinitionBuilder.create(RESOLVED_PATH, ModelType.STRING, false) .setStorageRuntime() .build(), SimpleAttributeDefinitionBuilder.create(AVAILABLE_SPACE, ModelType.DOUBLE, false) .setMeasurementUnit(unit) .setStorageRuntime() .build()) .build(); }).collect(Collectors.toList()); OperationDefinition operation = new SimpleOperationDefinitionBuilder(OPERATION_NAME, new DiskUsagePathResourceDescriptionResolver(OPERATION_NAME, replyParameters)) .addParameter(UNIT_ATTRIBUTE) .setReadOnly() .setRuntimeOnly() .setReplyType(ModelType.OBJECT) .setReplyParameters(replyParameters.toArray(new AttributeDefinition[replyParameters.size()])) .build(); resourceRegistration.registerOperationHandler(operation, handler); } public static class Builder { private final List<RelativePathSizeAttribute> attributes = new ArrayList<>(); private AttributeDefinition parentAttribute = null; private final PathManager pathManager; private Builder(PathManager pathManager) { this.pathManager = pathManager; } public static Builder of(final PathManager pathManager) { return new Builder(pathManager); } public Builder setParentAttribute(final AttributeDefinition parentAttribute) { this.parentAttribute = parentAttribute; return this; } public Builder addAttribute(final AttributeDefinition pathAttribute, final AttributeDefinition relativeToAttribute) { attributes.add(new RelativePathSizeAttribute(pathAttribute, relativeToAttribute)); return this; } public PathInfoHandler build() { if (attributes.isEmpty()) { attributes.add(new RelativePathSizeAttribute(null, null)); } return new PathInfoHandler(pathManager, parentAttribute, attributes); } } private static class RelativePathSizeAttribute { private final AttributeDefinition relativeToAttribute; private final AttributeDefinition pathAttribute; RelativePathSizeAttribute(final AttributeDefinition pathAttribute, final AttributeDefinition relativeToAttribute) { if (relativeToAttribute == null) { this.relativeToAttribute = PathResourceDefinition.RELATIVE_TO; } else { this.relativeToAttribute = relativeToAttribute; } if (pathAttribute == null) { this.pathAttribute = PathResourceDefinition.PATH; } else { this.pathAttribute = pathAttribute; } } } private static class DiskUsagePathResourceDescriptionResolver extends StandardResourceDescriptionResolver { private final String operationName; private final Set<String> replyParameterKeys; public DiskUsagePathResourceDescriptionResolver(final String operationName, List<AttributeDefinition> replyParameters) { super(FILESYSTEM, ControllerResolver.RESOURCE_NAME, ResolvePathHandler.class.getClassLoader(), false, false); this.operationName = operationName; replyParameterKeys = replyParameters.stream().map(AttributeDefinition::getName).collect(Collectors.toSet()); } @Override public String getOperationDescription(String operationName, Locale locale, ResourceBundle bundle) { if (this.operationName.equals(operationName)) { return bundle.getString(getKey()); } return super.getOperationParameterDescription(operationName, operationName, locale, bundle); } @Override public String getOperationParameterDescription(final String operationName, final String paramName, final Locale locale, final ResourceBundle bundle) { if (this.operationName.equals(operationName)) { return bundle.getString(getKey(paramName)); } return super.getOperationParameterDescription(operationName, paramName, locale, bundle); } @Override public String getOperationReplyDescription(String operationName, Locale locale, ResourceBundle bundle) { if (this.operationName.equals(operationName)) { return bundle.getString(getKey(PATH)); } return super.getOperationReplyDescription(operationName, locale, bundle); } @Override public String getResourceAttributeDescription(String attributeName, Locale locale, ResourceBundle bundle) { String attribute = attributeName; if(attributeName.endsWith(USED_SPACE)) { String key = attributeName.substring(0, attributeName.length() - USED_SPACE.length() -1); if(replyParameterKeys.contains(key)) { attribute = PATH + '.' + USED_SPACE; } } else if (attributeName.endsWith(AVAILABLE_SPACE)) { String key = attributeName.substring(0, attributeName.length() - AVAILABLE_SPACE.length() -1); if(replyParameterKeys.contains(key)) { attribute = PATH + '.' + AVAILABLE_SPACE; } } else if (attributeName.endsWith(CREATION_TIME)) { String key = attributeName.substring(0, attributeName.length() - CREATION_TIME.length() -1); if(replyParameterKeys.contains(key)) { attribute = PATH + '.' + CREATION_TIME; } } else if (attributeName.endsWith(RESOLVED_PATH)) { String key = attributeName.substring(0, attributeName.length() - RESOLVED_PATH.length() -1); if(replyParameterKeys.contains(key)) { attribute = PATH + '.' + RESOLVED_PATH; } } else if (attributeName.endsWith(LAST_MODIFIED)) { String key = attributeName.substring(0, attributeName.length() - LAST_MODIFIED.length() -1); if(replyParameterKeys.contains(key)) { attribute = PATH + '.' + LAST_MODIFIED; } } return super.getResourceAttributeDescription(attribute, locale, bundle); } private String getKey() { return String.format("%s.%s", FILESYSTEM, OPERATION_NAME); } private String getKey(final String key) { if(replyParameterKeys.contains(key)) { return String.format("%s.%s.%s", FILESYSTEM, OPERATION_NAME, PATH); } return String.format("%s.%s.%s", FILESYSTEM, OPERATION_NAME, key); } } }