Java tutorial
/* * This file is part of Mockey, a tool for testing application * interactions over HTTP, with a focus on testing web services, * specifically web applications that consume XML, JSON, and HTML. * * Copyright (C) 2009-2010 Authors: * * chad.lafontaine (chad.lafontaine AT gmail DOT com) * neil.cronin (neil AT rackle DOT com) * lorin.kobashigawa (lkb AT kgawa DOT com) * rob.meyer (rob AT bigdis DOT com) * * 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package com.mockey.storage.xml; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.google.common.io.CharStreams; import com.mockey.model.ProxyServerModel; import com.mockey.model.Scenario; import com.mockey.model.Service; import com.mockey.model.ServicePlan; import com.mockey.model.ServiceRef; import com.mockey.model.TwistInfo; import com.mockey.model.Url; import com.mockey.storage.IMockeyStorage; import com.mockey.storage.StorageRegistry; import com.mockey.ui.ServiceMergeResults; /** * Consumes an XML file and configures Mockey services. * * <pre> * + mock_service_definitions.xml * + mockey_def_depot * ++ <SERVICE NAME> * ++ <SERVICE NAME> * +++ <NAME>.xml * +++ scenarios * ++++ 1.xml * ++++ 2.xml * * </pre> * * @author Chad.Lafontaine * */ public class MockeyXmlFileManager { private static Logger logger = Logger.getLogger(MockeyXmlFileManager.class); private static final char[] VALID_FILE_NAME_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" .toCharArray(); private File basePathFile = new File(System.getProperty("user.dir")); private static IMockeyStorage store = StorageRegistry.MockeyStorage; private static MockeyXmlFileManager mockeyXmlFileManagerInstance = null; public static final String MOCK_SERVICE_DEFINITION = "mock_service_definitions.xml"; protected static final String MOCK_SERVICE_FOLDER = "mockey_def_depot"; protected static final String MOCK_SERVICE_SCENARIO_FOLDER = "scenarios"; public static final String FILESEPERATOR = System.getProperty("file.separator"); /** * Basic constructor. Will create a folder on the file system to store XML * definitions. * */ private MockeyXmlFileManager(String path) { if (path != null && path.trim().length() > 0) { this.basePathFile = new File(path); if (!this.basePathFile.exists()) { this.basePathFile.mkdir(); } } else { this.basePathFile = new File(System.getProperty("user.dir")); } File fileDepot = new File(this.getBasePathFile(), MOCK_SERVICE_FOLDER); if (!fileDepot.exists()) { boolean success = fileDepot.mkdirs(); if (!success) { logger.fatal("Unable to create a folder called " + MOCK_SERVICE_FOLDER); } else { logger.info("Created directory: " + fileDepot.getAbsolutePath()); } } } public static MockeyXmlFileManager getInstance() { if (MockeyXmlFileManager.mockeyXmlFileManagerInstance == null) { MockeyXmlFileManager.createInstance(System.getProperty("user.dir")); } return MockeyXmlFileManager.mockeyXmlFileManagerInstance; } /** * * @param path */ public static void createInstance(String path) { MockeyXmlFileManager.mockeyXmlFileManagerInstance = new MockeyXmlFileManager(path); } /** * * @return location of Mockey definitions. */ public File getBasePathFile() { return this.basePathFile; } /** * * @param file * - xml configuration file for Mockey * @throws IOException * @throws SAXException * @throws SAXParseException */ public String getFileContentAsString(InputStream fstream) throws IOException, SAXParseException, SAXException { String inputStreamString = CharStreams.toString(new InputStreamReader(fstream, "UTF-8")); return inputStreamString; } /** * Loads from default file definition file. * * @return results of loading configuration, includes additions and possible * conflicts. * * @throws SAXParseException * @throws IOException */ public ServiceMergeResults loadConfiguration() throws SAXParseException, IOException { File n = new File(this.getBasePathFile(), MOCK_SERVICE_DEFINITION); logger.debug("Loading configuration from " + MOCK_SERVICE_DEFINITION); try { return loadConfigurationWithXmlDef(getFileContentAsString(new FileInputStream(n)), null); } catch (SAXException e) { logger.error("Ouch, unable to parse" + n.getAbsolutePath(), e); } return new ServiceMergeResults(); } /** * * @param strXMLDefintion * @param tagArguments * @return results (conflicts and additions). * @throws IOException * @throws SAXParseException * @throws SAXException */ public ServiceMergeResults loadConfigurationWithXmlDef(String strXMLDefintion, String tagArguments) throws IOException, SAXParseException, SAXException { ServiceMergeResults mergeResults = new ServiceMergeResults(); // ***** REMEMBER ***** // Every time a saveOrUpdateXXXX is made, the entire STORE is written to // the file system. // If the STORE has many definitions, then each SAVE will loop over // every file and write. // // NOT GOOD FOR PERFORMANCE // // Solution: put the store in a temporary transient state // (memory-mode-only), then revert to original transient setting, // which could have been in memory-only or write-to-file in the // first place. // // ********************* Boolean originalTransientState = store.getReadOnlyMode(); store.setReadOnlyMode(true); // STEP #1. CREATE A TEMP STORE // Read the incoming XML file, and create a new/temporary store for the // need to ensure current store doesn't get overridden // MockeyXmlFileConfigurationReader msfr = new MockeyXmlFileConfigurationReader(); IMockeyStorage mockServiceStoreTemporary = msfr.readDefinition(strXMLDefintion); // STEP #2. PROXY SETTINGS // If the proxy settings are _empty_, then set the incoming // proxy settings. Otherwise, call out a merge conflict. // ProxyServerModel proxyServerModel = store.getProxy(); if (proxyServerModel.hasSettings()) { mergeResults.addConflictMsg("Proxy settings NOT set from incoming file."); } else { store.setProxy(mockServiceStoreTemporary.getProxy()); mergeResults.addAdditionMsg("Proxy settings set."); } // STEP #3. BUILD SERVICE REFERENCES // Why is this needed? // We are adding _new_ services into the Store, and that means that the // store's state is always changing. We need references as a saved // snapshot list of store state prior to adding new services. // ********** // I forget why we really need this though... // ********** List<Service> serviceListFromRefs = new ArrayList<Service>(); for (ServiceRef serviceRef : mockServiceStoreTemporary.getServiceRefs()) { try { String mockServiceDefinition = getFileContentAsString( new FileInputStream(serviceRef.getFileName())); // HACK: // I tried to find an easier way to use XML ENTITY and let // Digester // to the work of slurping up the XML but was unsuccessful. // Hence, the brute force. // YYYYY // List<Service> tmpList = msfr.readServiceDefinition(mockServiceDefinition); for (Service tmpService : tmpList) { serviceListFromRefs.add(tmpService); } } catch (SAXParseException spe) { logger.error("Unable to parse file of name " + serviceRef.getFileName(), spe); mergeResults.addConflictMsg("File not parseable: " + serviceRef.getFileName()); } catch (FileNotFoundException fnf) { logger.error("File not found: " + serviceRef.getFileName()); mergeResults.addConflictMsg("File not found: " + serviceRef.getFileName()); } } addServicesToStore(mergeResults, serviceListFromRefs, tagArguments); // STEP #4. MERGE SERVICES AND SCENARIOS // Since this gets complicated, logic was moved to it's own method. mergeResults = addServicesToStore(mergeResults, mockServiceStoreTemporary.getServices(), tagArguments); // STEP #5. UNIVERSAL RESPONSE SETTINGS // Important: usage of the temporary-store's Scenario reference // information is used to set the primary in-memory store. The primary // store has all the information and the TEMP store only needs to pass // the references, e.g. Service 1, Scenario 2. if (store.getUniversalErrorScenario() != null && mockServiceStoreTemporary.getUniversalErrorScenarioRef() != null) { mergeResults.addConflictMsg("Universal error message already defined with name '" + store.getUniversalErrorScenario().getScenarioName() + "'"); } else if (store.getUniversalErrorScenario() == null && mockServiceStoreTemporary.getUniversalErrorScenarioRef() != null) { store.setUniversalErrorScenarioRef(mockServiceStoreTemporary.getUniversalErrorScenarioRef()); mergeResults.addAdditionMsg("Universal error response defined."); } // STEP #6. MERGE SERVICE PLANS for (ServicePlan servicePlan : mockServiceStoreTemporary.getServicePlans()) { if (tagArguments != null) { servicePlan.addTagToList(tagArguments); } store.saveOrUpdateServicePlan(servicePlan); } // STEP #7. TWIST CONFIGURATION for (TwistInfo twistInfo : mockServiceStoreTemporary.getTwistInfoList()) { store.saveOrUpdateTwistInfo(twistInfo); } // STEP #8. DEFAULT Service Plan ID // Only set a default service plan ID from the incoming XML file // if one is not already set in the current store. ServicePlan servicePlan = mockServiceStoreTemporary .getServicePlanById(mockServiceStoreTemporary.getDefaultServicePlanIdAsLong()); if (servicePlan != null && store.getDefaultServicePlanIdAsLong() == null) { // OK, we have a 'default' service plan from the incoming file AND // the current store does not. Let's update the current store. store.setDefaultServicePlanId(mockServiceStoreTemporary.getDefaultServicePlanId()); store.setServicePlan(servicePlan); } // Don't forget to set state back to original state. // NOTE: if transient state (read only) is false, then this method will // write to STORE to the file system. // Yeah! // ********************* store.setReadOnlyMode(originalTransientState); // ********************* return mergeResults; } // Let's Merge! private ServiceMergeResults addServicesToStore(ServiceMergeResults mergeResults, List<Service> serviceListToAdd, String tagArguments) { // When loading a definition file, by default, we should // compare the uploaded Service list mock URL to what's currently // in memory. // // 1) MATCHING MOCK URL // If there is an existing/matching mockURL, then this isn't // a new service and we DON'T want to overwrite. But, we // want new Scenarios if they exist. See Scenario.equals() // // 2) NO MATCHING MOCK URL // If there is no matching service URL, then we want to create a new // service and associated scenarios. But here's an odd case. What if // we are merging two same-name Services, each with empty matching URL // lists? // for (Service uploadedServiceBean : serviceListToAdd) { List<Service> serviceBeansInMemory = store.getServices(); Iterator<Service> inMemoryServiceIter = serviceBeansInMemory.iterator(); boolean existingService = false; Service inMemoryServiceBean = null; while (inMemoryServiceIter.hasNext()) { inMemoryServiceBean = (Service) inMemoryServiceIter.next(); // Same name? if (uploadedServiceBean.getServiceName().trim().toLowerCase() .equals(inMemoryServiceBean.getServiceName().trim().toLowerCase())) { existingService = true; mergeResults.addConflictMsg("Service '" + uploadedServiceBean.getServiceName() + "' not created because one with the same name already defined. '" + inMemoryServiceBean.getServiceName() + "' "); } } if (!existingService) { // YES, no in-store matching Name. // We null ID, to not write-over on any in-store // services with same ID uploadedServiceBean.setId(null); // #TAG HANDLING - BEGIN // Ensure Service, and all it's child scenarios have // incoming/uploaded tag arguments uploadedServiceBean.addTagToList(tagArguments); for (Scenario scenarioTmp : uploadedServiceBean.getScenarios()) { scenarioTmp.addTagToList(tagArguments); } // #TAG HANDLING - END // Save to the IN-MEMORY STORE store.saveOrUpdateService(uploadedServiceBean); mergeResults.addAdditionMsg( "Uploaded Service '" + uploadedServiceBean.getServiceName() + "' created with scenarios."); } else { // We have an existing Service // Just merge scenarios per matching services mergeResults = this.mergeServices(uploadedServiceBean, inMemoryServiceBean, mergeResults, tagArguments); } } return mergeResults; } /** * This method will make an effort to take things that exist in the * * @param uploadedService * @param inMemoryService * @param readResults * @return */ public ServiceMergeResults mergeServices(Service uploadedService, Service inMemoryService , ServiceMergeResults readResults, String tagArguments) { Boolean originalMode = store.getReadOnlyMode(); store.setReadOnlyMode(true); if (uploadedService != null && inMemoryService != null && uploadedService.getServiceName().trim().equalsIgnoreCase(inMemoryService.getServiceName().trim()) ) { // ********************** TAG - BEGIN *********************** // #TAG HANDLING for the Service - BEGIN // Ensure Service gets incoming/uploaded-file tag arguments inMemoryService.addTagToList(tagArguments); // Ensure Service gets uploaded Service tag arguments inMemoryService.addTagToList(uploadedService.getTag()); // #TAG HANDLING for the Service - END // ********************** TAG - END ***************** // ********************* SCENARIOS BEGIN ******************* if (readResults == null) { readResults = new ServiceMergeResults(); } Iterator<Scenario> uploadedListIter = uploadedService.getScenarios().iterator(); Iterator<Scenario> inMemListIter = inMemoryService.getScenarios().iterator(); while (uploadedListIter.hasNext()) { Scenario uploadedScenario = (Scenario) uploadedListIter.next(); boolean inMemScenarioExistTemp = false; Scenario inMemScenarioTemp = null; while (inMemListIter.hasNext()) { inMemScenarioTemp = (Scenario) inMemListIter.next(); if (inMemScenarioTemp.hasSameNameAndResponse(uploadedScenario)) { inMemScenarioExistTemp = true; break; } } if (!inMemScenarioExistTemp) { // Hey, we have a new scenario. // NOTE: incoming/uploaded scenario has an ID. // We MUST nullify it, to ensure there's no common Service's // scenario's ID uploadedScenario.setId(null); uploadedScenario.setServiceId(inMemoryService.getId()); // Tag for Service:Scenario uploadedScenario.addTagToList(tagArguments); inMemoryService.saveOrUpdateScenario(uploadedScenario); readResults.addAdditionMsg("Scenario name '" + uploadedScenario.getScenarioName() + "' from uploaded service named '" + uploadedService.getServiceName() + "' was merged into service '" + inMemoryService.getServiceName() + "' "); } else { // OK, we have a MATCHING Scenario. // Be sure to add the uploaded-file tags inMemScenarioTemp.addTagToList(tagArguments); // Be sure to add the uploaded-scenario tags inMemScenarioTemp.addTagToList(uploadedScenario.getTag()); // Save the scenario to the Service inMemoryService.saveOrUpdateScenario(inMemScenarioTemp); // Although we still need to readResults.addConflictMsg("Uploaded Scenario '" + uploadedScenario.getScenarioName() + "' not added, already defined in in-memory service '" + inMemoryService.getServiceName() + "' "); } } // ********************* SCENARIOS - END ****************** // ********************* REAL URLS - BEGIN ******************* for (Url uploadedUrl : uploadedService.getRealServiceUrls()) { inMemoryService.saveOrUpdateRealServiceUrl(uploadedUrl); } // ********************* REAL URLS - END ******************* store.saveOrUpdateService(inMemoryService); } store.setReadOnlyMode(originalMode); return readResults; } /** * * @param service * @return */ protected File getServiceFile(Service service) { // Ensure the name is good. String serviceFileName = service.getServiceName(); if (serviceFileName != null) { serviceFileName = getSafeForFileSystemName(serviceFileName); File serviceDirectoryFile = new File(this.getBasePathFile(), MockeyXmlFileManager.MOCK_SERVICE_FOLDER + FILESEPERATOR + serviceFileName); // depot directory/<service ID> directory if (!serviceDirectoryFile.exists()) { serviceDirectoryFile.mkdir(); } // depot directory/<service ID> directory/scenario directory/ File serviceScenarioListDirectory = new File( serviceDirectoryFile.getPath() + FILESEPERATOR + MOCK_SERVICE_SCENARIO_FOLDER); if (!serviceScenarioListDirectory.exists()) { serviceScenarioListDirectory.mkdir(); } // depot directory/<service ID> directory/<service ID> file File serviceFile = new File(serviceDirectoryFile.getPath() + FILESEPERATOR + serviceFileName + ".xml"); return serviceFile; } else { return null; } } protected File[] getServiceScenarioFileNames(Service service) { File serviceScenarioDir = new File(this.getBasePathFile(), getSafeForFileSystemName(service.getServiceName()) + FILESEPERATOR + MOCK_SERVICE_SCENARIO_FOLDER); return serviceScenarioDir.listFiles(); } private File getServiceScenarioDirectoryAbsolutePath(Service service, Scenario scenario) { // mockey_def_depot/<service ID>/scenarios/<scenario_name>.xml File serviceScenarioFolder = new File(this.getBasePathFile(), MockeyXmlFileManager.MOCK_SERVICE_FOLDER + FILESEPERATOR + getSafeForFileSystemName(service.getServiceName()) + FILESEPERATOR + MOCK_SERVICE_SCENARIO_FOLDER); return serviceScenarioFolder; } protected File getServiceScenarioFileAbsolutePath(Service service, Scenario scenario) { // mockey_def_depot/<service ID>/scenarios/<scenario_name>.xml File serviceScenarioFolder = getServiceScenarioDirectoryAbsolutePath(service, scenario); File serviceScenarioFile = new File( serviceScenarioFolder.getPath() + FILESEPERATOR + getScenarioXmlFileName(scenario)); return serviceScenarioFile; } /** * Example if file is here: * <pre> * // /Users/<User>/Work/Mockey/dist/mockey_def_depot/<ServiceName>/scenarios/<ScenarioName>.xml * </pre> * then returns * <pre> * mockey_def_depot/<ServiceName>/scenarios/<ScenarioName>.xml * </pre> * @param service * @param scenario * @return a path name relative to the root mockey depot folder. * */ protected String getServiceScenarioFileRelativePathToDepotFolder(Service service, Scenario scenario) { String relativePath = MockeyXmlFileManager.MOCK_SERVICE_FOLDER + FILESEPERATOR + getSafeForFileSystemName(service.getServiceName()) + FILESEPERATOR + MOCK_SERVICE_SCENARIO_FOLDER + FILESEPERATOR + getSafeForFileSystemName(scenario.getScenarioName()) + ".xml"; return relativePath; } /** * * @param scenario * @return */ public String getScenarioResponseFileName(Scenario scenario) { return getSafeForFileSystemName(scenario.getScenarioName()) + ".txt"; } /** * * @param scenario * @return */ public String getScenarioXmlFileName(Scenario scenario) { return getSafeForFileSystemName(scenario.getScenarioName()) + ".xml"; } /** * * @param arg * @return a file name safe for a file system. * @see MockeyXmlFileManager#VALID_FILE_NAME_CHARS * */ public static String getSafeForFileSystemName(String arg) { // Let's make sure we only accept valid characters (AlphaNumberic + // '_'). StringBuffer safe = new StringBuffer(); for (int x = 0; x < arg.length(); x++) { boolean valid = false; for (int i = 0; i < VALID_FILE_NAME_CHARS.length; i++) { if (arg.charAt(x) == VALID_FILE_NAME_CHARS[i]) { valid = true; break; } } if (valid) { safe.append(arg.charAt(x)); } } return safe.toString().toLowerCase(); // } /** * @param baseFile * @param fileArg * @return the RELATIVE path of the fileArg if a child of base file. * Otherwise, returns the absolute path of the fileArg. */ public String getRelativePath(File fileArg) { String relativePath = ""; String basePath = this.getBasePathFile().getAbsolutePath(); String filePath = fileArg.getAbsolutePath(); int index = filePath.indexOf(basePath); if (index > -1) { try { // relativePath = fileArg.getAbsolutePath().substring(index + basePath.length() + 1); } catch (Exception e) { logger.error("Unable to retrive a relative path from: " + fileArg.getAbsolutePath() + "' with base path '" + basePath + "'", e); relativePath = "ERROR"; } } else { relativePath = "ERROR"; logger.error("Unable to retrive a relative path from: " + fileArg.getAbsolutePath() + "' with base path '" + basePath + "'"); } return relativePath; } }