001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.workspace; 017 018import java.io.BufferedReader; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.net.URL; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.Map; 030import java.util.Set; 031 032import javax.xml.XMLConstants; 033import javax.xml.parsers.SAXParserFactory; 034import javax.xml.validation.Schema; 035import javax.xml.validation.SchemaFactory; 036 037import org.apache.avalon.framework.configuration.Configuration; 038import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041import org.xml.sax.XMLReader; 042 043import org.ametys.runtime.servlet.RuntimeConfig; 044 045 046/** 047 * Main entry point for access to workspaces. 048 */ 049public final class WorkspaceManager 050{ 051 // shared instance 052 private static WorkspaceManager __manager; 053 054 private static final String __WORKSPACE_FILENAME = "workspace.xml"; 055 056 /** 057 * Cause of the deactivation of a workspace 058 */ 059 public enum InactivityCause 060 { 061 /** 062 * Constant for excluded workspaces 063 */ 064 EXCLUDED, 065 /** 066 * Constant for workspaces having error in their declaration 067 */ 068 MISDECLARED, 069 /** 070 * Constant for workspaces having error in their workspace.xml file 071 */ 072 MISCONFIGURED 073 } 074 075 // Map<workspaceName, baseURI> 076 private Map<String, Workspace> _workspaces = new HashMap<>(); 077 078 private Map<String, InactivityCause> _inactiveWorkspaces = new HashMap<>(); 079 080 private Logger _logger = LoggerFactory.getLogger(WorkspaceManager.class); 081 082 083 private WorkspaceManager() 084 { 085 // empty constructor 086 } 087 088 /** 089 * Returns the shared instance of the <code>WorkspaceManager</code> 090 * @return the shared instance of the <code>WorkspaceManager</code> 091 */ 092 public static WorkspaceManager getInstance() 093 { 094 if (__manager == null) 095 { 096 __manager = new WorkspaceManager(); 097 } 098 099 return __manager; 100 } 101 102 /** 103 * Returns all active workspaces names 104 * @return all active workspaces names 105 */ 106 public Set<String> getWorkspaceNames() 107 { 108 return Collections.unmodifiableSet(_workspaces.keySet()); 109 } 110 111 /** 112 * Returns active workspaces declarations. 113 * @return active workspaces declarations. 114 */ 115 public Map<String, Workspace> getWorkspaces() 116 { 117 return Collections.unmodifiableMap(_workspaces); 118 } 119 120 /** 121 * Return the inactive workspaces 122 * @return All the inactive workspaces 123 */ 124 public Map<String, InactivityCause> getInactiveWorkspaces() 125 { 126 return Collections.unmodifiableMap(_inactiveWorkspaces); 127 } 128 129 /** 130 * Returns a String array containing the ids of the plugins embedded in jars 131 * @return a String array containing the ids of the plugins embedded in jars 132 */ 133 public Set<String> getEmbeddedWorskpacesIds() 134 { 135 return _workspaces.keySet(); 136 } 137 138 /** 139 * Returns the base URI associated with the given workspace, or null if the workspace does not exist 140 * @param workspaceName the name of the working workspace 141 * @return the URI associated with the given workspace 142 */ 143 public String getBaseURI(String workspaceName) 144 { 145 return _workspaces.get(workspaceName).getEmbededLocation(); 146 } 147 148 /** 149 * Returns the workspace filesystem location for the given workspace or null if the workspace is loaded from the classpath. 150 * @param workspaceName the workspace name 151 * @return the workspace location for the given workspace 152 */ 153 public File getLocation(String workspaceName) 154 { 155 return _workspaces.get(workspaceName).getExternalLocation(); 156 } 157 158 /** 159 * Initialize the WorkspaceManager.<br> 160 * It first looks for META-INF/runtime-workspace files in the classpath, then in the "workspaces" directory of the application. 161 * @param excludedWorkspace the excluded workspaces, as given by the RuntimeConfig 162 * @param contextPath the servlet context path 163 * @throws IOException if an error occurs while retrieving resources 164 */ 165 public void init(Collection<String> excludedWorkspace, String contextPath) throws IOException 166 { 167 _workspaces.clear(); 168 169 // Begin with workspace embedded in jars 170 Enumeration<URL> workspaceResources = getClass().getClassLoader().getResources("META-INF/ametys-workspaces"); 171 while (workspaceResources.hasMoreElements()) 172 { 173 URL workspaceResource = workspaceResources.nextElement(); 174 _initResourceWorkspace(excludedWorkspace, workspaceResource); 175 } 176 177 // Then workspace from the "<context>/workspaces" directory 178 File workspacesDir = new File(contextPath, "workspaces"); 179 if (workspacesDir.exists() && workspacesDir.isDirectory()) 180 { 181 for (File workspace : new File(contextPath, "workspaces").listFiles()) 182 { 183 _initFileWorkspaces(workspace, workspace.getName(), excludedWorkspace); 184 } 185 } 186 187 Map<String, File> externalWorkspaces = RuntimeConfig.getInstance().getExternalWorkspaces(); 188 189 // external workspaces 190 for (String externalWorkspace : externalWorkspaces.keySet()) 191 { 192 File workspaceDir = externalWorkspaces.get(externalWorkspace); 193 194 if (workspaceDir.exists() && workspaceDir.isDirectory()) 195 { 196 _initFileWorkspaces(workspaceDir, externalWorkspace, excludedWorkspace); 197 } 198 else 199 { 200 throw new RuntimeException("The configured external workspace is not an existing directory: " + workspaceDir.getAbsolutePath()); 201 } 202 } 203 } 204 205 private void _initResourceWorkspace(Collection<String> excludedWorkspace, URL workspaceResource) throws IOException 206 { 207 try (InputStream is = workspaceResource.openStream(); 208 BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) 209 { 210 String workspaceString; 211 while ((workspaceString = br.readLine()) != null) 212 { 213 int i = workspaceString.indexOf(':'); 214 if (i != -1) 215 { 216 String workspaceName = workspaceString.substring(0, i); 217 String workspaceBaseURI = workspaceString.substring(i + 1); 218 219 if (!excludedWorkspace.contains(workspaceName)) 220 { 221 if (_workspaces.containsKey(workspaceName)) 222 { 223 String errorMessage = "The workspace named " + workspaceName + " already exists"; 224 _logger.error(errorMessage); 225 throw new IllegalArgumentException(errorMessage); 226 } 227 228 URL workspaceConfigurationURL = getClass().getResource(workspaceBaseURI + "/" + __WORKSPACE_FILENAME); 229 if (workspaceConfigurationURL == null || getClass().getResource(workspaceBaseURI + "/" + __WORKSPACE_FILENAME) == null) 230 { 231 if (_logger.isWarnEnabled()) 232 { 233 _logger.warn("A workspace '" + workspaceName + "' is declared in a library, but files '" + __WORKSPACE_FILENAME + "' and/or 'sitemap.xmap' are missing at '" + workspaceBaseURI + "'. Workspace will be ignored."); 234 } 235 _inactiveWorkspaces.put(workspaceName, InactivityCause.MISDECLARED); 236 return; 237 } 238 239 Configuration workspaceConfiguration = null; 240 try (InputStream is2 = workspaceConfigurationURL.openStream()) 241 { 242 workspaceConfiguration = _getConfigurationFromStream(workspaceName, is2, workspaceBaseURI); 243 } 244 245 if (workspaceConfiguration != null) 246 { 247 Workspace workspace = new Workspace(workspaceName, workspaceBaseURI); 248 workspace.configure(workspaceConfiguration); 249 250 _workspaces.put(workspaceName, workspace); 251 252 if (_logger.isInfoEnabled()) 253 { 254 _logger.info("Workspace '" + workspaceName + "' registered at '" + workspaceBaseURI + "'"); 255 } 256 } 257 else 258 { 259 _inactiveWorkspaces.put(workspaceName, InactivityCause.MISCONFIGURED); 260 } 261 } 262 else 263 { 264 _inactiveWorkspaces.put(workspaceName, InactivityCause.EXCLUDED); 265 } 266 } 267 } 268 } 269 } 270 271 private void _initFileWorkspaces(File workspaceFile, String workspaceName, Collection<String> excludedWorkspace) throws IOException 272 { 273 File workspaceConfigurationFile = new File(workspaceFile, __WORKSPACE_FILENAME); 274 if (excludedWorkspace.contains(workspaceName)) 275 { 276 _inactiveWorkspaces.put(workspaceName, InactivityCause.EXCLUDED); 277 } 278 else if (workspaceFile.exists() && workspaceFile.isDirectory() && workspaceConfigurationFile.exists() && new File(workspaceFile, "sitemap.xmap").exists()) 279 { 280 if (_workspaces.containsKey(workspaceName)) 281 { 282 String errorMessage = "The workspace named " + workspaceFile.getName() + " already exists"; 283 _logger.error(errorMessage); 284 throw new IllegalArgumentException(errorMessage); 285 } 286 287 Configuration workspaceConfiguration = null; 288 try (InputStream is = new FileInputStream(workspaceConfigurationFile)) 289 { 290 workspaceConfiguration = _getConfigurationFromStream(workspaceName, is, workspaceConfigurationFile.getAbsolutePath()); 291 } 292 293 if (workspaceConfiguration != null) 294 { 295 Workspace workspace = new Workspace(workspaceName, workspaceFile); 296 workspace.configure(workspaceConfiguration); 297 298 _workspaces.put(workspaceName, workspace); 299 300 if (_logger.isInfoEnabled()) 301 { 302 _logger.info("Workspace '" + workspaceName + "' registered at '" + workspaceConfigurationFile.getAbsolutePath() + "'"); 303 } 304 } 305 else 306 { 307 _inactiveWorkspaces.put(workspaceName, InactivityCause.MISCONFIGURED); 308 } 309 } 310 else 311 { 312 if (_logger.isWarnEnabled()) 313 { 314 _logger.warn("Workspace '" + workspaceName + "' registered at '" + workspaceFile.getAbsolutePath() + "' has no '" + __WORKSPACE_FILENAME + "' file or no 'sitemap.xmap' file."); 315 } 316 317 _inactiveWorkspaces.put(workspaceName, InactivityCause.MISDECLARED); 318 } 319 } 320 321 private Configuration _getConfigurationFromStream(String workspaceName, InputStream is, String path) 322 { 323 try 324 { 325 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 326 URL schemaURL = getClass().getResource("workspace-4.0.xsd"); 327 Schema schema = schemaFactory.newSchema(schemaURL); 328 SAXParserFactory factory = SAXParserFactory.newInstance(); 329 factory.setNamespaceAware(true); 330 factory.setSchema(schema); 331 XMLReader reader = factory.newSAXParser().getXMLReader(); 332 DefaultConfigurationBuilder confBuilder = new DefaultConfigurationBuilder(reader); 333 334 return confBuilder.build(is, path); 335 } 336 catch (Exception e) 337 { 338 _logger.error("Unable to access to workspace '" + workspaceName + "' at " + path, e); 339 return null; 340 } 341 } 342 343}