/** Copyright (C) 2012 Delcyon, Inc. 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 com.delcyon.capo.resourcemanager; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Vector; import java.util.logging.Level; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import com.delcyon.capo.CapoApplication; import com.delcyon.capo.Configuration; import com.delcyon.capo.CapoApplication.Location; import com.delcyon.capo.Configuration.PREFERENCE; import com.delcyon.capo.annotations.DefaultDocumentProvider; import com.delcyon.capo.annotations.DirectoyProvider; import com.delcyon.capo.controller.ControlElement; import com.delcyon.capo.controller.Group; import com.delcyon.capo.controller.elements.ResourceControlElement; import com.delcyon.capo.preferences.Preference; import com.delcyon.capo.preferences.PreferenceInfo; import com.delcyon.capo.preferences.PreferenceInfoHelper; import com.delcyon.capo.preferences.PreferenceProvider; import com.delcyon.capo.resourcemanager.ResourceDescriptor.Action; import com.delcyon.capo.resourcemanager.ResourceDescriptor.LifeCycle; import com.delcyon.capo.resourcemanager.remote.RemoteResourceType; import com.delcyon.capo.resourcemanager.types.ContentMetaData; import com.delcyon.capo.resourcemanager.types.FileResourceType; import com.delcyon.capo.xml.cdom.CElement; /** * @author jeremiah * */ @PreferenceProvider(preferences=ResourceManager.Preferences.class) @DirectoyProvider(preferenceName="RESOURCE_DIR",preferences=Configuration.PREFERENCE.class,location=Location.SERVER) public class ResourceManager extends CapoDataManager { public enum Preferences implements Preference { @PreferenceInfo(arguments={"type"}, defaultValue="file", description="URI scheme name to use if no scheme is present. ", longOption="DEFAULT_RESOURCE_TYPE", option="DEFAULT_RESOURCE_TYPE") DEFAULT_RESOURCE_TYPE; @Override public String[] getArguments() { return PreferenceInfoHelper.getInfo(this).arguments(); } @Override public String getDefaultValue() { return PreferenceInfoHelper.getInfo(this).defaultValue(); } @Override public String getDescription() { return PreferenceInfoHelper.getInfo(this).description(); } @Override public String getLongOption() { return PreferenceInfoHelper.getInfo(this).longOption(); } @Override public String getOption() { return PreferenceInfoHelper.getInfo(this).option(); } @Override public Location getLocation() { return PreferenceInfoHelper.getInfo(this).location(); } } private ResourceDescriptor dataDir; private transient Transformer transformer; private HashMap<String, ResourceDescriptor> directoryHashMap = new HashMap<String, ResourceDescriptor>(); private HashMap<String,ResourceType> schemeResourceTypeHashMap = new HashMap<String, ResourceType>(); private ResourceControlElement resourceManagerControlElement; private String defaultResourceTypeScheme; public ResourceManager() throws Exception { loadResourceTypes(); //setup xml output TransformerFactory tFactory = TransformerFactory.newInstance(); transformer = tFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); Group group = new Group("resourceManager", null, null, null); CElement resourceManagerControlElementDeclaration = new CElement(CapoApplication.SERVER_NAMESPACE_URI, "server:resource"); resourceManagerControlElementDeclaration.setAttribute(ResourceControlElement.Attributes.lifeCycle, LifeCycle.EXPLICIT); resourceManagerControlElement = new ResourceControlElement(); resourceManagerControlElement.init(resourceManagerControlElementDeclaration, null, group, null); //Build and load directories //no place should use append resource URI's like this except here where the resource manager hasn't been loaded yet, so auto resolution can't happen. dataDir = getResourceDescriptor(null,CapoApplication.getConfiguration().getValue(PREFERENCE.CAPO_DIR)); ContentMetaData dataDirContentMetaData = dataDir.getResourceMetaData(null); if (dataDirContentMetaData.exists() == false) { //don't create this directory if auto sync is disabled. if(CapoApplication.getConfiguration().hasOption(PREFERENCE.DISABLE_CONFIG_AUTOSYNC) == false) { dataDir.performAction(null, Action.CREATE,new ResourceParameter(ResourceDescriptor.DefaultParameters.CONTAINER, "true")); dataDir.close(null); dataDir.open(null); } } directoryHashMap.put(PREFERENCE.CAPO_DIR.toString(), dataDir); } @Override public void release() throws Exception { resourceManagerControlElement.destroy(); } @Override public void init(Boolean... minimal) throws Exception { boolean initMinimalOnly = false; HashMap<String, String> multiInitHashMap = null; if(minimal != null && minimal.length >= 1 ) { initMinimalOnly = minimal[0]; //load directory providers and see which ones correspond to directory preferences that are actionable this round multiInitHashMap = new HashMap<>(); Set<String> directoryProvidersSet = CapoApplication.getAnnotationMap().get(DirectoyProvider.class.getCanonicalName()); if (directoryProvidersSet != null) { for (String className : directoryProvidersSet) { try { if(Class.forName(className).getAnnotation(DirectoyProvider.class).canUseRepository() != initMinimalOnly) { multiInitHashMap.put(Class.forName(className).getAnnotation(DirectoyProvider.class).preferenceName(),""); } else { CapoApplication.logger.log(Level.FINE, "Skipping: "+className+" until repo ready"); } } catch (Exception exception){ exception.printStackTrace(); } } } } //build dynamic directories Preference[] directoryPreferences = CapoApplication.getConfiguration().getDirectoryPreferences(); for (Preference preference : directoryPreferences) { if(multiInitHashMap != null) { if(multiInitHashMap.containsKey(preference.toString()) == false) { continue; } } ResourceDescriptor dynamicDir = null;//dataDir.getChildResourceDescriptor(resourceManagerControlElement,CapoApplication.getConfiguration().getValue(preference)); if(multiInitHashMap != null && initMinimalOnly == false) //rewrite to use repo { dynamicDir = getResourceDescriptor(resourceManagerControlElement, "repo:/"+CapoApplication.getConfiguration().getValue(preference)); dynamicDir.init(null, null, LifeCycle.EXPLICIT, false); } else { dynamicDir = dataDir.getChildResourceDescriptor(resourceManagerControlElement,CapoApplication.getConfiguration().getValue(preference)); } ContentMetaData dynamicDirContentMetaData = dynamicDir.getResourceMetaData(null); if (dynamicDirContentMetaData.exists() == false) { dynamicDir.performAction(null, Action.CREATE,new ResourceParameter(ResourceDescriptor.DefaultParameters.CONTAINER, "true")); dynamicDir.close(null); dynamicDir.open(null); } directoryHashMap.put(preference.toString(), dynamicDir); } //verify and create default documents DefaultDocumentProvider[] defaultDocumentProviders = CapoApplication.getConfiguration().getDefaultDocumentProviders(); for (DefaultDocumentProvider defaultDocumentProvider : defaultDocumentProviders) { if (defaultDocumentProvider.location() != Location.BOTH) { if (defaultDocumentProvider.location() == Location.CLIENT && CapoApplication.isServer() == true) { continue; } if (defaultDocumentProvider.location() == Location.SERVER && CapoApplication.isServer() == false) { continue; } } Preference preference = CapoApplication.getConfiguration().getPreference(defaultDocumentProvider.directoryPreferenceName()); String[] fileNames = defaultDocumentProvider.name().split(","); for (String fileName : fileNames) { if(directoryHashMap.get(preference.toString()) == null) { if(initMinimalOnly == true) { continue; } } ResourceDescriptor dynamicFile = directoryHashMap.get(preference.toString()).getChildResourceDescriptor(null,fileName); if (dynamicFile.getResourceMetaData(null).exists() == false) { Document document = CapoApplication.getDefaultDocument(fileName); OutputStream outputStream = dynamicFile.getOutputStream(null); transformer.transform(new DOMSource(document), new StreamResult(outputStream)); outputStream.close(); } } } } private void loadResourceTypes() throws Exception { Set<String> resourceTypeProvidersSet = CapoApplication.getAnnotationMap().get(ResourceTypeProvider.class.getCanonicalName()); if (resourceTypeProvidersSet != null) { for (String className : resourceTypeProvidersSet) { try { String[] schemes = Class.forName(className).getAnnotation(ResourceTypeProvider.class).schemes(); ResourceType resourceType = (ResourceType) Class.forName(className).newInstance(); for (String scheme : schemes) { CapoApplication.logger.log(Level.CONFIG, "loaded "+resourceType.getClass().getName()+" as '"+scheme+":' resourceType"); schemeResourceTypeHashMap.put(scheme.toLowerCase(), resourceType); } } catch (ClassNotFoundException classNotFoundException) { CapoApplication.logger.log(Level.WARNING, "Error getting resource type providers",classNotFoundException); } } } } @Override public ResourceDescriptor getResourceDirectory(String resourceDirectoryPreferenceName) { ResourceDescriptor resourceDescriptor = directoryHashMap.get(resourceDirectoryPreferenceName); try { return getResourceDescriptor(null, resourceDescriptor.getResourceURI().getBaseURI()); } catch (Exception e) { CapoApplication.logger.warning(e.getMessage()); return null; } } /** * Called from anyplace that needs a resource */ @Override public ResourceDescriptor getResourceDescriptor(ControlElement callingControlElement,String resourceURI) throws Exception { if(callingControlElement == null) { callingControlElement = resourceManagerControlElement; } String scheme = ResourceURI.getScheme(resourceURI); //if there is no scheme, and it didn't come from client request, assume it's a default resource type. Which unless overridden is a file:. if (scheme == null) { scheme = getDefaultResourceTypeScheme(); } //we get here when called from a place that uses a parsable uri ResourceType resourceType = getResourceType(scheme); if (resourceType != null) { if (resourceType instanceof RemoteResourceType) { RemoteResourceType remoteResourceType = (RemoteResourceType) resourceType; return remoteResourceType.getResourceDescriptor(callingControlElement.getControllerClientRequestProcessor(),resourceURI); } else { return resourceType.getResourceDescriptor(resourceURI); } } else { throw new Exception("No matching ResourceType for: "+resourceURI); } } @Override public ResourceType getResourceType(String scheme) { return schemeResourceTypeHashMap.get(scheme.toLowerCase()); } @Override public String getDefaultResourceTypeScheme() { if(defaultResourceTypeScheme == null) { return CapoApplication.getConfiguration().getValue(Preferences.DEFAULT_RESOURCE_TYPE); } else { return defaultResourceTypeScheme; } } @Override public void setDefaultResourceTypeScheme(String defaultResourceTypeScheme) { this.defaultResourceTypeScheme = defaultResourceTypeScheme; } /** * This gets called from ResourceElement when we encounter a resource element declaration */ public ResourceDescriptor createResourceDescriptor(ResourceControlElement resourceControlElement, ResourceParameter[] resourceParameters) throws Exception { //TODO verify our special brand of file URLs String resourceURI = null; if (resourceControlElement != null) { resourceURI = resourceControlElement.getURIValue(); } //figure out resource type String scheme = ResourceURI.getScheme(resourceURI); //if we don't know what this assume it's a default resource type. Which unless overridden is a file. if (resourceURI != null && scheme == null && resourceControlElement == null) { scheme = getDefaultResourceTypeScheme(); } ResourceType resourceType = schemeResourceTypeHashMap.get(scheme.toLowerCase()); if (resourceType != null) { ResourceDescriptor resourceDescriptor = resourceType.getResourceDescriptor(resourceURI); //TODO Application level resourceDescriptor caching //declaredResourceDecriptorHashMap.put(resourceElement.getName()+"@"+resourceElement.getURIValue(), resourceDescriptor); return resourceDescriptor; } else { CapoApplication.logger.log(Level.WARNING, "No matching ResourceType for: "+resourceURI); throw new Exception("No matching ResourceType for: "+resourceURI); } } //DOCUMENT PROCESSING @Override public ResourceDescriptor findDocumentResourceDescriptor(String documentName, String clientID, Preference directoryPreference) throws Exception { //remove extension from documentName documentName = documentName.replaceAll("(.*)\\.[Xx][MmSs][Ll]", "$1"); //check the client file system (if we have a client id) ResourceDescriptor foundResourceDescriptor = null; if (clientID != null) { ResourceDescriptor clientResourceDescriptor = getResourceDescriptor(null, "clients:"+clientID); //see if we have a child filesystem if (clientResourceDescriptor.getResourceMetaData(null).exists() == true) { ResourceDescriptor relaventChildResourceDescriptor = clientResourceDescriptor.getChildResourceDescriptor(resourceManagerControlElement,CapoApplication.getConfiguration().getValue(directoryPreference)); if (relaventChildResourceDescriptor != null && relaventChildResourceDescriptor.getResourceMetaData(null).exists() == true) { //search the client filesystem foundResourceDescriptor = getDocumentResourceDescriptor(relaventChildResourceDescriptor, documentName); } } } //check server file system if wo don't have a document RD yet if (foundResourceDescriptor == null) { ResourceDescriptor relaventChildResourceDescriptor = getResourceDirectory(directoryPreference.toString()); if (relaventChildResourceDescriptor != null && relaventChildResourceDescriptor.getResourceMetaData(null).exists() == true) { //search the relevant file system foundResourceDescriptor = getDocumentResourceDescriptor(relaventChildResourceDescriptor, documentName); } } //if not null return parsed document if (foundResourceDescriptor != null && foundResourceDescriptor.getResourceMetaData(null).exists() == true) { return foundResourceDescriptor; } //both should call getDocument with an appropriate directory container //so this method should check for the existence of a container, then try them in order of priority, child, then server return foundResourceDescriptor; } @Override public Document findDocument(String documentName, String clientID, Preference directoryPreference) throws Exception { ResourceDescriptor documentResourceDescriptor = findDocumentResourceDescriptor(documentName, clientID, directoryPreference); if (documentResourceDescriptor != null) { Document document = CapoApplication.getDocumentBuilder().parse(documentResourceDescriptor.getInputStream(null)); document.setDocumentURI(documentResourceDescriptor.getResourceURI().getBaseURI()); documentResourceDescriptor.release(null); return document; } else { //if were STILL null, try class path //try class loaders if (documentName.matches(".*\\.[Xx][MmSs][Ll]") == false) { documentName = documentName+".xml"; } URL moduleURL = ClassLoader.getSystemResource(CapoApplication.getConfiguration().getValue(directoryPreference)+"/"+documentName); if (moduleURL != null) { Document document = CapoApplication.getDocumentBuilder().parse(moduleURL.openStream()); document.setDocumentURI(moduleURL.toString()); return document; } } return null; } private ResourceDescriptor getDocumentResourceDescriptor(ResourceDescriptor parentDirectory, String documentName) throws Exception { //bak here ContentMetaData contentMetaData = parentDirectory.getResourceMetaData(null); List<ContentMetaData> childResourceList = contentMetaData.getContainedResources(); for (ContentMetaData childContentMetaData : childResourceList) { if ((childContentMetaData.isContainer() == false || childContentMetaData.getLength() > 0) && childContentMetaData.getResourceURI().getPath().matches(".*/"+documentName+"\\.[Xx][MmSs][Ll]")) { return CapoApplication.getDataManager().getResourceDescriptor(null, childContentMetaData.getResourceURI().getResourceURIString()); } else if (childContentMetaData.isContainer() && childContentMetaData.getResourceURI().getPath().endsWith("/"+documentName)) { ResourceDescriptor moduleResourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(null, childContentMetaData.getResourceURI().getResourceURIString()); return getDocumentResourceDescriptor(moduleResourceDescriptor, documentName); } } return null; } @Override public List<ResourceDescriptor> findDocuments(ResourceDescriptor parentDirectory) throws Exception { Vector<ResourceDescriptor> resourceDescriptorVector = new Vector<ResourceDescriptor>(); ContentMetaData contentMetaData = parentDirectory.getResourceMetaData(null); List<ContentMetaData> childResourceList = contentMetaData.getContainedResources(); for (ContentMetaData childContentMetaData : childResourceList) { if (childContentMetaData.isContainer() == false && childContentMetaData.getResourceURI().getPath().matches(".*\\.[Xx][MmSs][Ll]")) { resourceDescriptorVector.add(CapoApplication.getDataManager().getResourceDescriptor(null, childContentMetaData.getResourceURI().getResourceURIString())); } else if (childContentMetaData.isContainer()) { ResourceDescriptor moduleResourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(null, childContentMetaData.getResourceURI().getResourceURIString()); resourceDescriptorVector.addAll(findDocuments(moduleResourceDescriptor)); } } return resourceDescriptorVector; } //SEQUENCE PROCESSING @Override public synchronized long nextValue(String sequenceName) throws Exception { ResourceDescriptor sequenceFile = getResourceDescriptor(null,sequenceName+".seq"); sequenceFile.addResourceParameters(null,new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.CONFIG_DIR)); if (sequenceFile.getResourceMetaData(null).exists() == false) { //initialize sequence file sequenceFile.performAction(null, Action.CREATE); sequenceFile.close(null); sequenceFile.open(null); OutputStream sequenceFileOutputStream = sequenceFile.getOutputStream(null); sequenceFileOutputStream.write("0".getBytes()); sequenceFileOutputStream.close(); } sequenceFile.open(null); InputStream sequenceFileInputStream = sequenceFile.getInputStream(null); byte[] buffer = new byte[4096]; sequenceFileInputStream.read(buffer); sequenceFileInputStream.close(); long nextValue = Long.parseLong(new String(buffer).trim()); nextValue++; OutputStream sequenceFileOutputStream = sequenceFile.getOutputStream(null); sequenceFileOutputStream.write((nextValue+"").getBytes()); sequenceFileOutputStream.close(); sequenceFile.close(null); return nextValue; } }