/* * Copyright © 2015-2016 Cask Data, Inc. * * 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 co.cask.cdap.metadata; import co.cask.cdap.common.InvalidMetadataException; import co.cask.cdap.common.NotFoundException; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.entity.EntityExistenceVerifier; import co.cask.cdap.data2.metadata.dataset.MetadataDataset; import co.cask.cdap.data2.metadata.store.MetadataStore; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.metadata.MetadataRecord; import co.cask.cdap.proto.metadata.MetadataScope; import co.cask.cdap.proto.metadata.MetadataSearchResultRecord; import co.cask.cdap.proto.metadata.MetadataSearchTargetType; import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import java.util.Map; import java.util.Set; /** * Implementation of {@link MetadataAdmin} that interacts directly with {@link MetadataStore}. */ public class DefaultMetadataAdmin implements MetadataAdmin { private static final CharMatcher KEY_AND_TAG_MATCHER = CharMatcher.inRange('A', 'Z') .or(CharMatcher.inRange('a', 'z')) .or(CharMatcher.inRange('0', '9')) .or(CharMatcher.is('_')) .or(CharMatcher.is('-')); private static final CharMatcher VALUE_MATCHER = CharMatcher.inRange('A', 'Z') .or(CharMatcher.inRange('a', 'z')) .or(CharMatcher.inRange('0', '9')) .or(CharMatcher.is('_')) .or(CharMatcher.is('-')) .or(CharMatcher.WHITESPACE); private final MetadataStore metadataStore; private final CConfiguration cConf; private final EntityExistenceVerifier entityExistenceVerifier; @Inject DefaultMetadataAdmin(MetadataStore metadataStore, CConfiguration cConf, EntityExistenceVerifier entityExistenceVerifier) { this.metadataStore = metadataStore; this.cConf = cConf; this.entityExistenceVerifier = entityExistenceVerifier; } @Override public void addProperties(Id.NamespacedId entityId, Map<String, String> properties) throws NotFoundException, InvalidMetadataException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); validateProperties(entityId, properties); metadataStore.setProperties(MetadataScope.USER, entityId, properties); } @Override public void addTags(Id.NamespacedId entityId, String... tags) throws NotFoundException, InvalidMetadataException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); validateTags(entityId, tags); metadataStore.addTags(MetadataScope.USER, entityId, tags); } @Override public Set<MetadataRecord> getMetadata(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return metadataStore.getMetadata(entityId); } @Override public Set<MetadataRecord> getMetadata(MetadataScope scope, Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return ImmutableSet.of(metadataStore.getMetadata(scope, entityId)); } @Override public Map<String, String> getProperties(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return metadataStore.getProperties(entityId); } @Override public Map<String, String> getProperties(MetadataScope scope, Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return metadataStore.getProperties(scope, entityId); } @Override public Set<String> getTags(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return metadataStore.getTags(entityId); } @Override public Set<String> getTags(MetadataScope scope, Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); return metadataStore.getTags(scope, entityId); } @Override public void removeMetadata(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); metadataStore.removeMetadata(MetadataScope.USER, entityId); } @Override public void removeProperties(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); metadataStore.removeProperties(MetadataScope.USER, entityId); } @Override public void removeProperties(Id.NamespacedId entityId, String... keys) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); metadataStore.removeProperties(MetadataScope.USER, entityId, keys); } @Override public void removeTags(Id.NamespacedId entityId) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); metadataStore.removeTags(MetadataScope.USER, entityId); } @Override public void removeTags(Id.NamespacedId entityId, String... tags) throws NotFoundException { entityExistenceVerifier.ensureExists(entityId.toEntityId()); metadataStore.removeTags(MetadataScope.USER, entityId, tags); } @Override public Set<MetadataSearchResultRecord> searchMetadata(String namespaceId, String searchQuery, Set<MetadataSearchTargetType> types) { return metadataStore.searchMetadataOnType(namespaceId, searchQuery, types); } @Override public Set<MetadataSearchResultRecord> searchMetadata(MetadataScope scope, String namespaceId, String searchQuery, Set<MetadataSearchTargetType> types) { return metadataStore.searchMetadataOnType(scope, namespaceId, searchQuery, types); } // Helper methods to validate the metadata entries. private void validateProperties(Id.NamespacedId entityId, Map<String, String> properties) throws InvalidMetadataException { for (Map.Entry<String, String> entry : properties.entrySet()) { // validate key validateKeyAndTagsFormat(entityId, entry.getKey()); validateTagReservedKey(entityId, entry.getKey()); validateLength(entityId, entry.getKey()); // validate value validateValueFormat(entityId, entry.getValue()); validateLength(entityId, entry.getValue()); } } private void validateTags(Id.NamespacedId entityId, String... tags) throws InvalidMetadataException { for (String tag : tags) { validateKeyAndTagsFormat(entityId, tag); validateLength(entityId, tag); } } /** * Validate that the key is not reserved {@link MetadataDataset#TAGS_KEY}. */ private void validateTagReservedKey(Id.NamespacedId entityId, String key) throws InvalidMetadataException { if (MetadataDataset.TAGS_KEY.equals(key.toLowerCase())) { throw new InvalidMetadataException(entityId, "Could not set metadata with reserved key " + MetadataDataset.TAGS_KEY); } } /** * Validate the key matches the {@link #KEY_AND_TAG_MATCHER} character test. */ private void validateKeyAndTagsFormat(Id.NamespacedId entityId, String keyword) throws InvalidMetadataException { if (!KEY_AND_TAG_MATCHER.matchesAllOf(keyword)) { throw new InvalidMetadataException(entityId, "Illegal format for the value : " + keyword); } } /** * Validate the value of a property matches the {@link #VALUE_MATCHER} character test. */ private void validateValueFormat(Id.NamespacedId entityId, String keyword) throws InvalidMetadataException { if (!VALUE_MATCHER.matchesAllOf(keyword)) { throw new InvalidMetadataException(entityId, "Illegal format for the value : " + keyword); } } /** * Validate that the key length does not exceed the configured limit. */ private void validateLength(Id.NamespacedId entityId, String keyword) throws InvalidMetadataException { // check for max char per value if (keyword.length() > cConf.getInt(Constants.Metadata.MAX_CHARS_ALLOWED)) { throw new InvalidMetadataException(entityId, "Metadata " + keyword + " should not exceed maximum of " + cConf.get(Constants.Metadata.MAX_CHARS_ALLOWED) + " characters."); } } }