package com.thinkbiganalytics.metadata.modeshape; /*- * #%L * thinkbig-metadata-modeshape * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import com.google.common.collect.Lists; import com.thinkbiganalytics.metadata.api.BaseProvider; import com.thinkbiganalytics.metadata.modeshape.common.JcrEntity; import com.thinkbiganalytics.metadata.modeshape.common.JcrObject; import com.thinkbiganalytics.metadata.modeshape.support.JcrPropertyUtil; import com.thinkbiganalytics.metadata.modeshape.support.JcrQueryUtil; import com.thinkbiganalytics.metadata.modeshape.support.JcrTool; import com.thinkbiganalytics.metadata.modeshape.support.JcrUtil; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.modeshape.jcr.api.JcrTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.StreamSupport; import javax.jcr.AccessDeniedException; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.query.QueryResult; /** */ public abstract class BaseJcrProvider<T, PK extends Serializable> implements BaseProvider<T, PK> { private static final Logger log = LoggerFactory.getLogger(BaseJcrProvider.class); private static final Pattern INVALID_SYSTEM_NAME_PATTERN = Pattern.compile("[^(A-Z)(a-z)(0-9)_-]"); protected Class<T> entityClass; protected Class<? extends JcrEntity> jcrEntityClass; public BaseJcrProvider() { this.entityClass = (Class<T>) getEntityClass(); this.jcrEntityClass = getJcrEntityClass(); } protected Session getSession() { return JcrMetadataAccess.getActiveSession(); } public void save() { try { getSession().save(); } catch (RepositoryException e) { log.error("Failed to save session state.", e); throw new MetadataRepositoryException("Failed to save the current session state.", e); } } public abstract Class<? extends T> getEntityClass(); public abstract Class<? extends JcrEntity> getJcrEntityClass(); /** * return the JCR NodeType for this entity (i.e. tba:category, tba:feed) */ public abstract String getNodeType(Class<? extends JcrEntity> jcrEntityType); /** * \ * Gets the entity class appropriate for the given node type if polymophic types are * supported by the provider implementation. By default if simply returns the result * of getJcrEntityClass(). * * @return the appropriate entity class */ public Class<? extends JcrEntity> getJcrEntityClass(String jcrNodeType) { return getJcrEntityClass(); } /** * \ * Gets the entity class appropriate for the given node polymophic types are * supported by the provider implementation. By default if simply returns the result * of getJcrEntityClass(String nodeType) by calling getPrimaryNodeType().name() on the node. * * @return the appropriate entity class */ public Class getJcrEntityClass(Node node) { try { return getJcrEntityClass(node.getPrimaryNodeType().getName()); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to determine type of node: " + node, e); } } /** * Tests whether an Entity Node exists for a Parent Path and relative Path. */ public boolean hasEntityNode(String parentPath, String relPath) { Session session = getSession(); try { Node typesNode = session.getNode(parentPath); return JcrUtil.hasNode(typesNode, relPath); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to create new entity of type: " + getEntityClass(), e); } } /** * Creates a new Entity Node object for a Parent Path, relative Path and node type */ public Node findOrCreateEntityNode(String parentPath, String relPath, Class<? extends JcrEntity> jcrEntityType) { Session session = getSession(); try { Node typesNode = session.getNode(parentPath); JcrTools tools = new JcrTool(); Node entNode = tools.findOrCreateChild(typesNode, relPath, getNodeType(jcrEntityType)); return entNode; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to create new entity of type: " + getEntityClass(), e); } } public T findOrCreateEntity(String path, String relPath, Map<String, Object> props) { return findOrCreateEntity(path, relPath, props, null); } public T findOrCreateEntity(String path, String relPath, Map<String, Object> props, Object... constructorArgs) { return findOrCreateEntity(path, relPath, getJcrEntityClass(), props, constructorArgs); } public T findOrCreateEntity(String path, String relPath, Class<? extends JcrEntity> entClass) { return findOrCreateEntity(path, relPath, entClass, null); } public T findOrCreateEntity(String path, String relPath, Class<? extends JcrEntity> entClass, Map<String, Object> props, Object... constructorArgs) { Session session = getSession(); Node entNode = findOrCreateEntityNode(path, relPath, entClass); entNode = JcrPropertyUtil.setProperties(session, entNode, props); Class<? extends JcrEntity> actualClass = getJcrEntityClass(entNode); // Handle subtypes return (T) JcrUtil.createJcrObject(entNode, actualClass, constructorArgs); } public Node getNodeByIdentifier(PK id) { try { Node node = getSession().getNodeByIdentifier(id.toString()); return node; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failure while finding entity by ID: " + id, e); } } @Override public T findById(PK id) { try { Node node = null; try { node = getSession().getNodeByIdentifier(id.toString()); } catch (ItemNotFoundException e) { //swallow this exception // if we dont find the item then return null } if (node != null) { return (T) constructEntity(node); } else { return null; } } catch (AccessDeniedException e) { log.debug("Access denied to feed with ID: {}", id, e); return null; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failure while finding entity by ID: " + id, e); } } protected T constructEntity(Node node) { @SuppressWarnings("unchecked") T entity = (T) constructEntity(node, getJcrEntityClass(node)); return entity; } protected <T extends JcrObject> T constructEntity(Node node, Class<T> entityClass) { return JcrUtil.createJcrObject(node, entityClass); } public List<T> findWithExplainPlan(String queryExpression) { try { org.modeshape.jcr.api.query.Query query = (org.modeshape.jcr.api.query.Query) getSession().getWorkspace().getQueryManager().createQuery(queryExpression, "JCR-SQL2"); org.modeshape.jcr.api.query.QueryResult result = query.explain(); String plan = result.getPlan(); log.info(plan); return find(queryExpression); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failure while finding entity ", e); } } public List<Node> findNodes(String query) { return Lists.newArrayList(findIterableNodes(query)); } public List<T> find(String query) { List<T> entities = new ArrayList<>(); try { QueryResult result = JcrQueryUtil.query(getSession(), query); if (result != null) { NodeIterator nodeIterator = result.getNodes(); while (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); T entity = constructEntity(node); entities.add(entity); } } return entities; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to findAll for Type : " + getNodeType(getJcrEntityClass()), e); } } public <D, R> Iterable<D> findIterable(String query, Class<D> domainClass, Class<R> resultClass) { return () -> { return StreamSupport.stream(findIterableNodes(query).spliterator(), false) .map(node -> { try { @SuppressWarnings("unchecked") D entity = (D) ConstructorUtils.invokeConstructor(resultClass, node); return entity; } catch (Exception e) { throw new MetadataRepositoryException("Failed to create entity: " + resultClass, e); } }) .iterator(); }; } public T findFirst(String query) { try { QueryResult result = JcrQueryUtil.query(getSession(), query); if (result != null) { NodeIterator nodeIterator = result.getNodes(); if (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); T entity = constructEntity(node); return entity; } } return null; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to findAll for Type : " + getNodeType(getJcrEntityClass()), e); } } public <T extends JcrObject> T findFirst(String query, Class<T> resultClass) { try { QueryResult result = JcrQueryUtil.query(getSession(), query); if (result != null) { NodeIterator nodeIterator = result.getNodes(); if (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); T entity = constructEntity(node, resultClass); return entity; } } return null; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to findAll for Type : " + getNodeType(getJcrEntityClass()), e); } } @Override public List<T> findAll() { String jcrQuery = "SELECT * FROM [" + getNodeType(getJcrEntityClass()) + "]"; return find(jcrQuery); } @Override public T create(T t) { try { getSession().save(); return t; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to save session", e); } } @Override public T update(T t) { try { getSession().save(); return t; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to save session", e); } } @Override public void delete(T t) { if (t != null) { if (t instanceof JcrObject) { JcrObject jcrObject = (JcrObject) t; jcrObject.remove(); } else { throw new UnsupportedOperationException(); } } } @Override public void deleteById(PK id) { T item = findById(id); delete(item); } public Iterable<Node> findIterableNodes(String query) { return () -> { try { QueryResult result = JcrQueryUtil.query(getSession(), query); @SuppressWarnings("unchecked") Iterator<Node> itr = (Iterator<Node>) result.getNodes(); return itr; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failure executing query: " + query, e); } }; } protected String sanitizeSystemName(String systemName) { return INVALID_SYSTEM_NAME_PATTERN.matcher(systemName).replaceAll("_"); } protected String sanitizeTitle(String title) { return StringEscapeUtils.escapeJava(title); } }