Java tutorial
/** * Copyright (C) 2008 GIP RECIA (Groupement d'Intrt Public REgion * Centre InterActive) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ /** * */ package org.esco.grouper.parsing; import edu.internet2.middleware.grouper.Stem; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.esco.grouper.dao.GrouperDAO; import org.esco.grouper.domain.beans.EvaluableString; import org.esco.grouper.domain.beans.GroupOrFolderDefinition; import org.esco.grouper.domain.beans.GroupOrFolderDefinitionsManager; import org.esco.grouper.domain.beans.MembersDefinition; import org.esco.grouper.domain.beans.PersonType; import org.esco.grouper.domain.beans.PrivilegeDefinition; import org.esco.grouper.domain.beans.TemplateElement; import org.esco.grouper.exceptions.UnknownTemplateElementTempateElement; import org.esco.grouper.utils.GrouperSessionUtil; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.LocatorImpl; import org.xml.sax.helpers.XMLReaderFactory; /** * Content handler used read the defintion of groups, folders, administration elements, etc, * for the Sarapis Group Service (SGS). * @author GIP RECIA - A. Deman * 28 juil. 08 * */ public class SGSParsingUtil extends org.xml.sax.helpers.DefaultHandler implements InitializingBean, EntityResolver { /** Constant use to go up in the path. */ private static final String UP_IN_PATH = "..:"; /** Constants to use the same path. */ private static final String CURRENT_PATH = "."; /** Constants to use the same path (escaped for replacements). */ private static final String ESC_CURRENT_PATH = "\\."; /** User tag. */ private static final String USER_TAG = "user"; /** Folder tag. */ private static final String FOLDER_TAG = "folder"; /** Folder template tag. */ private static final String FOLDER_TEMPL_TAG = "folder-template"; /** Group tag. */ private static final String GROUP_TAG = "group"; /** Group template tag. */ private static final String GROUP_TEMPL_TAG = "group-template"; /** Privilege tag. */ private static final String PRIVILEGE_TAG = "privilege"; /** Member of tag. */ private static final String MEMBER_OF_TAG = "member-of"; /** Members tag. */ private static final String MEMBERS_TAG = "members"; /** Template element tag. */ private static final String TEMPLATE_ELT_TAG = "template-element"; /** Delete empty folders tag. */ private static final String DEL_EMPTY_FOLD_TAG = "delete-empty-folders"; /** Delete empty groups tag. */ private static final String DEL_EMPTY_GROUPS_TAG = "delete-empty-groups"; /** Delete empty groups tag. */ private static final String FORCE_PRIV_TAG = "force-privileges"; /** Logger. */ private static final Logger LOGGER = Logger.getLogger(SGSParsingUtil.class); /** Locator. */ private Locator locator; /** Recursive administration paths. */ private Set<PrivilegeDefinition> recPrivileges = new HashSet<PrivilegeDefinition>(); /** Starting path of recursive privileges. */ private Map<String, List<PrivilegeDefinition>> startOfRecPrivileges = new HashMap<String, List<PrivilegeDefinition>>(); /** The group or folder definitions manager. */ private GroupOrFolderDefinitionsManager definitionsManager; /** The attribute handler. */ private SGSAttributeHandler attributeHandler = new SGSAttributeHandler(); /** The current group or folder folder under construction. */ private GroupOrFolderDefinition current; /** The containing path. */ private String currentPath = ""; /** The URI of the xml file that contains the definitions for the folder, groups, administration * privileges and memberships. */ private String definitionsFileURI; /** The grouper session util to parameter. */ private GrouperSessionUtil grouperSessionUtil; /** The Grouper dao instance to parameter. */ private GrouperDAO grouperDAO; /** * Builds an instance of SGSParsingUtil. */ public SGSParsingUtil() { super(); locator = new LocatorImpl(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creation of a ContentHandler: " + getClass().getSimpleName() + "."); } } /** * Checks the data injection. * @throws Exception * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { Assert.notNull(this.grouperSessionUtil, "property grouperSessionUtil of class " + this.getClass().getName() + " can not be null."); Assert.notNull(this.grouperDAO, "property grouperUtil of class " + this.getClass().getName() + " can not be null."); Assert.notNull(this.definitionsManager, "property definitionManager of class " + this.getClass().getName() + " can not be null."); Assert.notNull(this.definitionsFileURI, "property definitionsFileURI of class " + this.getClass().getName() + " can not be null."); } /** * Error method. * @param e The initial exception. * @throws SAXException The initial exception. */ @Override public void error(final SAXParseException e) throws SAXException { LOGGER.debug(e, e); throw e; } /** * Receives notification of the end of a document. * The validity of the path references, for administration or memebrships are checked * at this step. * @throws SAXException * @see org.xml.sax.ContentHandler#endDocument() */ @Override public void endDocument() throws SAXException { LOGGER.debug("End of parsing."); final List<String> errors = definitionsManager.checksPathsReferences(); if (!errors.isEmpty()) { for (String error : errors) { LOGGER.fatal(error); } throw new SAXParseException( "Error(s) detected while checking the validity of " + "the paths references (" + errors.size() + " error(s) detected). See the log file for more details.", locator); } } /** * Receives notification of the end of an element. * @param nameSpaceURI the Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName localName the local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qualifiedName The qualified XML name (with prefix), or the * empty string if qualified names are not available. * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ @Override public void endElement(final String nameSpaceURI, final String localName, final String qualifiedName) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closing Tag: " + localName); } if (FOLDER_TAG.equals(localName) || FOLDER_TEMPL_TAG.equals(localName)) { // Removes the recursive administrating path for wich // this folder is the starting point. final List<PrivilegeDefinition> recPrivilegesForCurrent = startOfRecPrivileges.get(currentPath); if (recPrivilegesForCurrent != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Removing recursive administrating path for " + currentPath + ": " + recPrivilegesForCurrent + "."); } for (PrivilegeDefinition rp : recPrivilegesForCurrent) { recPrivileges.remove(rp); } startOfRecPrivileges.remove(currentPath); } else if (LOGGER.isDebugEnabled()) { LOGGER.debug("No recursiveprivileges path for " + currentPath + "."); } // Returns to the previous path. final int index = currentPath.lastIndexOf(Stem.DELIM); if (index > 0) { currentPath = currentPath.substring(0, index); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Returning to the previous path: " + currentPath); } } } /** * Resolve relative path. * @param tag The current processed tag. * @param path The path to resolve. * @return The resolved path. * @throws SAXException If the path can't be resolved. */ protected String resolvePath(final String tag, final String path) throws SAXException { // Resolves the paths with ../../../reference/to/a/folder/ if (path.startsWith(UP_IN_PATH)) { // Error the current path is not set. String relativePath = path; String currentReference = current.getPath(); while (relativePath.startsWith(UP_IN_PATH)) { final int index = currentReference.lastIndexOf(Stem.DELIM); // Error can't go up. if (index < 0) { final String msg = "Error while resolving path: " + path + " - too much \"" + UP_IN_PATH + "\"(Tag " + tag + " - line " + locator.getLineNumber() + ")."; LOGGER.fatal(msg); throw new SAXException(msg); } currentReference = currentReference.substring(0, index); relativePath = relativePath.replaceFirst(UP_IN_PATH, ""); } if (!relativePath.startsWith(Stem.DELIM)) { relativePath = Stem.DELIM + relativePath; } return currentReference + relativePath; } // The path is relative and referes to the current path. if (path.startsWith(CURRENT_PATH)) { String resolvedPath = path.replaceFirst(CURRENT_PATH, current.getPath()); return resolvedPath.replaceAll(ESC_CURRENT_PATH, ""); } // The path is absolute. return path; } /** * Setter for the object for locating the origin of SAX document events. * @param locator The new locator to use. * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) */ @Override public void setDocumentLocator(final Locator locator) { this.locator = locator; } /** * Start of the document. * @see org.xml.sax.ContentHandler#startDocument() */ @Override public void startDocument() { LOGGER.debug("Starting the parsing."); } /** * Registers a template element. */ protected void handleTemplateElement() { if (attributeHandler.getDefaultValue() == null) { // Registers a template element without default value. TemplateElement.registerTemplateElement(attributeHandler.getKey()); } else { // Registers a template element with a default value. TemplateElement.registerTemplateElement(attributeHandler.getKey(), attributeHandler.getDefaultValue()); } } /** * Handles the privilege informations. * @throws SAXException If there is a parsing exception. * @throws UnknownTemplateElementTempateElement If the definition of the * privilege used a template element wich is not defined. */ protected void handlePrivileges() throws SAXException, UnknownTemplateElementTempateElement { // Checks the path. if (attributeHandler.getPath() == null || "".equals(attributeHandler.getPath())) { handleAttributeError(PRIVILEGE_TAG, SGSAttributeHandler.PATH_ATTR); } // Adds privilege to the current group or folder definition. final String resolvedPath = resolvePath(PRIVILEGE_TAG, attributeHandler.getPath()); final PrivilegeDefinition privilege = new PrivilegeDefinition(attributeHandler.getRight(), new EvaluableString(resolvedPath)); current.addPrivilege(privilege); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding privilege: " + privilege + " to the group or folder: " + current.getPath()); } // The privilege must registered as a recursive one. if (attributeHandler.getRecursive()) { // Error if the recursive administring path is already defined. if (recPrivileges.contains(privilege)) { String originalStart = ""; final Iterator<String> iter = startOfRecPrivileges.keySet().iterator(); boolean found = false; while (iter.hasNext() && !found) { final String start = iter.next(); final List<PrivilegeDefinition> recPriv = startOfRecPrivileges.get(start); if (recPriv.contains(privilege)) { originalStart = start; found = true; } } final String msg = "Encounters recursive privilege " + privilege + " for: " + currentPath + " which is already defined for: " + originalStart + " (line " + locator.getLineNumber() + ")."; LOGGER.error(msg); throw new SAXParseException(msg, locator); } recPrivileges.add(privilege); List<PrivilegeDefinition> privilegesForCurrent = startOfRecPrivileges.get(currentPath); if (privilegesForCurrent == null) { privilegesForCurrent = new ArrayList<PrivilegeDefinition>(); startOfRecPrivileges.put(currentPath, privilegesForCurrent); } privilegesForCurrent.add(privilege); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering starting point " + currentPath + " for recursive administrating path: " + attributeHandler.getPath() + "."); } } } /** * Handles the definition of who has to be a member of a group. * @throws SAXParseException If there is a parsing error. */ protected void handleMembersRule() throws SAXParseException { // The tag members-of should contains : // Required : Type = ALL | TEACHER | STUDENT | TOS | ADMINISTRATIVE | PARENT // Optional : distribution-by = %aTemplateElement% if (attributeHandler.getType() == null) { handleAttributeError(MEMBERS_TAG, SGSAttributeHandler.TYPE_ATTR, PersonType.values(), null); } if (attributeHandler.getDistributionBy() != null) { if (!TemplateElement.isTemplateElement(attributeHandler.getDistributionBy())) { handleAttributeError(MEMBERS_TAG, SGSAttributeHandler.DISTRIB_BY_ATTR, TemplateElement.getAvailableTemplateElements().toArray(), attributeHandler.getDistributionBy()); } } // Adds the definition of the members. final String templateKey = attributeHandler.getDistributionBy(); final TemplateElement templateElt = TemplateElement.getAvailableTemplateelementByKey(templateKey); final MembersDefinition membersDef = new MembersDefinition(attributeHandler.getType(), templateElt); current.addMembersDefinition(membersDef); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding members definition " + membersDef + " to the group: " + current.getPath()); } } /** * Handles a membership definition. * @throws UnknownTemplateElementTempateElement If the definition of the membership refers to * an undifined template elment. * @throws SAXException If an error occures during the parsing process. */ protected void handleMembership() throws UnknownTemplateElementTempateElement, SAXException { // Checks that the current definition denotes a group. if (current.isFolder()) { handleAttributeError(MEMBER_OF_TAG, SGSAttributeHandler.PATH_ATTR, "!!! Not allowed for a folder", null); } // Cheks the path this group is member of. if (attributeHandler.getPath() == null || "".equals(attributeHandler.getPath())) { handleAttributeError(MEMBER_OF_TAG, SGSAttributeHandler.PATH_ATTR); } // Handles relative paths. final String resolvedPath = resolvePath(MEMBER_OF_TAG, attributeHandler.getPath()); current.addContainingGroupPath(resolvedPath, attributeHandler.getConditionGroupExt()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding containing group path " + resolvedPath + "with condition " + attributeHandler.getConditionGroupExt() + " to the group: " + current.getPath()); } } /** * Handles the definition of a (template) group or (template) folder. * @param localName the local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @throws SAXParseException If there is a parsing error. * @throws UnknownTemplateElementTempateElement If the definition use an undefined template element. */ protected void handleGroupOrFolder(final String localName) throws SAXParseException, UnknownTemplateElementTempateElement { // Checks that the attributes are valid. if ("".equals(attributeHandler.getExtension())) { handleAttributeError(localName, SGSAttributeHandler.EXT_ATTR, "", attributeHandler.getExtension()); } if ("".equals(attributeHandler.getDisplayExtension())) { handleAttributeError(localName, SGSAttributeHandler.DISP_EXT_ATTR, "", attributeHandler.getExtension()); } if ("".equals(attributeHandler.getDescription())) { handleAttributeError(localName, SGSAttributeHandler.DESC_ATTR, "", attributeHandler.getExtension()); } final boolean isFolder = FOLDER_TAG.equals(localName) || FOLDER_TEMPL_TAG.equals(localName); current = new GroupOrFolderDefinition(isFolder, attributeHandler.getPreexisting(), attributeHandler.getCreate(), currentPath, attributeHandler.getExtension(), attributeHandler.getDisplayExtension(), attributeHandler.getDescription()); // Adds the recursive administrating path if needed. for (PrivilegeDefinition recPrivilege : recPrivileges) { current.addPrivilege(recPrivilege); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding recursive privilege: " + recPrivilege + " to the group or folder: " + current.getPath()); } } // Registers the defintion to the defintion manager. definitionsManager.registerDefinition(current); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Folder creation: " + current); } if (isFolder) { currentPath = current.getPath(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("New current path " + currentPath); } } } /** * Recieves the notification of the start of an element. * @param uri the Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName the local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName the qualified name (with prefix), or the * empty string if qualified names are not available. * @param atts the attributes attached to the element. If * there are no attributes, it shall be an empty * Attributes object. The value of this object after * startElement returns is undefined. * @throws org.xml.sax.SAXException any SAX exception, possibly * wrapping another exception. * @see org.xml.sax.ContentHandler * #startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Opening TAG = " + localName); } attributeHandler.reset(); attributeHandler.handleAttributes(locator, atts); try { if (USER_TAG.equals(localName)) { // User for the Grouper sessions. LOGGER.debug("The user " + attributeHandler.getUid() + " will be used for the Groupers sessions."); grouperSessionUtil.setSubjectId(attributeHandler.getUid()); } else if (DEL_EMPTY_FOLD_TAG.equals(localName)) { // Deletion of the empty folders. LOGGER.debug("Setting delete empty folders to: " + attributeHandler.getValue()); grouperDAO.setDeleteEmptyFolders(attributeHandler.getValue()); } else if (DEL_EMPTY_GROUPS_TAG.equals(localName)) { // Deletion of the empty groups. LOGGER.debug("Setting delete empty groups to: " + attributeHandler.getValue()); grouperDAO.setDeleteEmptyGroups(attributeHandler.getValue()); } else if (FORCE_PRIV_TAG.equals(localName)) { // Force privileges flag. LOGGER.debug("Setting force privileges to: " + attributeHandler.getValue()); grouperDAO.setForcePrivileges(attributeHandler.getValue()); } else if (TEMPLATE_ELT_TAG.equals(localName)) { // Definition of a template element. handleTemplateElement(); } else if (PRIVILEGE_TAG.equals(localName)) { // Definition of an administration privilege. handlePrivileges(); } else if (MEMBERS_TAG.equalsIgnoreCase(localName)) { // Definition of the members of a group. handleMembersRule(); } else if (MEMBER_OF_TAG.equals(localName)) { // Definition of a membership of a group. handleMembership(); } else if (FOLDER_TAG.equals(localName) || FOLDER_TEMPL_TAG.equals(localName) || GROUP_TAG.equals(localName) || GROUP_TEMPL_TAG.equals(localName)) { // Definition of a group, a template group, a folder or a template folder. handleGroupOrFolder(localName); } } catch (UnknownTemplateElementTempateElement e) { // Error on a template element. final String msg = "Error while parsing a string with template element - Tag: " + localName + " - line: " + locator.getLineNumber(); LOGGER.fatal(msg); throw new SAXParseException(msg, locator, e); } } /** * Handles an attribute error. * @param tag The current tag. * @param attribute The attribute. * @throws SAXParseException The thrown exception. */ private void handleAttributeError(final String tag, final String attribute) throws SAXParseException { handleAttributeError(tag, attribute, "", null); } /** * Handles an attribute error. * @param tag The current tag. * @param attribute The attribute. * @param legalValues The legal values for the attribute. * @param actualValue The actual value of the attribute. * @throws SAXParseException The thrown exception. */ private void handleAttributeError(final String tag, final String attribute, final Object[] legalValues, final String actualValue) throws SAXParseException { String legalValuesStr = null; if (legalValues != null) { legalValuesStr = Arrays.toString(legalValues); } handleAttributeError(tag, attribute, legalValuesStr, actualValue); } /** * Handles an attribute error. * @param tag The current tag. * @param attribute The attribute. * @param legalValues The string that represents the legal values for the attribute. * @param actualValue The actual value of the attribute. * @throws SAXParseException The thrown exception. */ private void handleAttributeError(final String tag, final String attribute, final String legalValues, final String actualValue) throws SAXParseException { String msg = "Tag: " + tag + " - Path of the tag: " + current.getPath() + " - Invalid value for the attribute {" + attribute + "}" + " (line " + locator.getLineNumber() + ")"; if (legalValues != null && !"".equals(legalValues)) { msg += " - Legal values are: " + legalValues; } if (actualValue != null) { msg += " - Actual value is: " + actualValue; } msg += "."; LOGGER.error(msg); throw new SAXParseException(msg, locator); } /** * Resolves an external entity. * @param publicId The public identifer, or null if none is available. * @param systemId The system identifier provided in the XML document. * @return The new input source. * @throws SAXException * @throws IOException * @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String, java.lang.String) */ @Override public InputSource resolveEntity(final String publicId, final String systemId) throws IOException, SAXException { if (systemId == null) { return null; } if (systemId.startsWith("classpath:") && systemId.endsWith(".dtd")) { final String dtdFile = systemId.substring(systemId.indexOf(':') + 1); LOGGER.debug("Try to load the DTD from the classpath: " + dtdFile + "."); final InputStream dtdIS = getClass().getClassLoader().getResourceAsStream(dtdFile); if (dtdIS == null) { LOGGER.fatal("Unable to load the DTD from the classpath: " + dtdFile); } return new InputSource(dtdIS); } return super.resolveEntity(publicId, systemId); } /** * Starts the parsing process. * @throws SAXException If there is an error during the parsing (invalid group defintion for instance). * @throws IOException If there is an IO error. */ public void parse() throws IOException, SAXException { XMLReader saxReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); saxReader.setFeature("http://xml.org/sax/features/validation", true); saxReader.setContentHandler(this); saxReader.setErrorHandler(this); saxReader.setEntityResolver(this); if (LOGGER.isInfoEnabled()) { LOGGER.info("---------------------------------"); LOGGER.info("Parsing definition file: "); LOGGER.info(definitionsFileURI); LOGGER.info("---------------------------------"); } InputStream iStream = getClass().getClassLoader().getResourceAsStream(definitionsFileURI); if (iStream == null) { LOGGER.fatal("Unable to load (from classpath) file: " + definitionsFileURI + "."); } //final InputSource is = saxReader.parse(new InputSource(iStream)); if (LOGGER.isInfoEnabled()) { LOGGER.info("---------------------------------"); LOGGER.info("Definition file parsed."); LOGGER.info("---------------------------------"); } } /** * Getter for definitionsManager. * @return definitionsManager. */ public GroupOrFolderDefinitionsManager getDefinitionsManager() { return definitionsManager; } /** * Setter for definitionsManager. * @param definitionsManager the new value for definitionsManager. */ public void setDefinitionsManager(final GroupOrFolderDefinitionsManager definitionsManager) { this.definitionsManager = definitionsManager; } /** * Getter for definitionsFileURI. * @return definitionsFileURI. */ public String getDefinitionsFileURI() { return definitionsFileURI; } /** * Setter for definitionsFileURI. * @param definitionsFileURI the new value for definitionsFileURI. */ public void setDefinitionsFileURI(final String definitionsFileURI) { this.definitionsFileURI = definitionsFileURI; } /** * Getter for grouperSessionUtil. * @return grouperSessionUtil. */ public GrouperSessionUtil getGrouperSessionUtil() { return grouperSessionUtil; } /** * Setter for grouperSessionUtil. * @param grouperSessionUtil the new value for grouperSessionUtil. */ public void setGrouperSessionUtil(final GrouperSessionUtil grouperSessionUtil) { this.grouperSessionUtil = grouperSessionUtil; } /** * Getter for grouperDAO. * @return grouperDAO. */ public GrouperDAO getGrouperDAO() { return grouperDAO; } /** * Setter for grouperDAO. * @param grouperDAO the new value for grouperDAO. */ public void setGrouperDAO(final GrouperDAO grouperDAO) { this.grouperDAO = grouperDAO; } }