2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.config;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.security.DigestInputStream;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.util.ArrayList;
34 import java.util.Enumeration;
35 import java.util.Hashtable;
36 import java.util.List;
38 import org.apache.commons.codec.binary.Hex;
39 import org.apache.commons.codec.digest.DigestUtils;
40 import org.apache.commons.io.FileUtils;
41 import org.collectionspace.services.common.api.JEEServerDeployment;
42 import org.collectionspace.services.common.api.Tools;
43 import org.collectionspace.services.common.context.ServiceBindingUtils;
44 import org.collectionspace.services.config.service.ServiceBindingType;
45 import org.collectionspace.services.config.service.ServiceObjectType;
46 import org.collectionspace.services.config.tenant.RepositoryDomainType;
47 import org.collectionspace.services.config.tenant.TenantBindingConfig;
48 import org.collectionspace.services.config.tenant.TenantBindingType;
49 import org.collectionspace.services.config.types.PropertyItemType;
51 import ch.elca.el4j.services.xmlmerge.Configurer;
52 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
53 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * ServicesConfigReader reads service layer specific configuration
61 * $LastChangedRevision: $ $LastChangedDate: $
63 public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl<List<TenantBindingType>> {
64 final public static boolean INCLUDE_CREATE_DISABLED_TENANTS = true;
65 final public static boolean EXCLUDE_CREATE_DISABLED_TENANTS = false;
67 final private static String TENANT_BINDINGS_ERROR = "Tenant bindings error(s) for tenant: ";
68 final private static String TENANT_BINDINGS_DELTA_FILENAME = JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX
70 final private static String MERGED_SUFFIX = ".merged.xml";
71 private static final String NO_SERVICE_BINDINGS_FOUND_ERR = "No Service bindings found.";
73 private static final Logger logger = LoggerFactory.getLogger(TenantBindingConfigReaderImpl.class);
74 private List<TenantBindingType> tenantBindingTypeList;
75 // tenant id, tenant binding, for tenants not marked as createDisabled
76 private Hashtable<String, TenantBindingType> enabledTenantBindings = new Hashtable<String, TenantBindingType>();
77 // tenant id, tenant binding
78 private Hashtable<String, TenantBindingType> allTenantBindings = new Hashtable<String, TenantBindingType>();
80 private Hashtable<String, RepositoryDomainType> domains = new Hashtable<String, RepositoryDomainType>();
81 // tenant-qualified servicename, service binding
82 private Hashtable<String, ServiceBindingType> serviceBindings = new Hashtable<String, ServiceBindingType>();
84 // tenant-qualified service object name to service name, service binding
85 private Hashtable<String, ServiceBindingType> docTypes = new Hashtable<String, ServiceBindingType>();
87 public TenantBindingConfigReaderImpl(String serverRootDir) {
92 public String getFileName() {
93 return TENANT_BINDINGS_DELTA_FILENAME;
96 private String getFileName(String tenantName, boolean useAppGeneratedBindings) {
97 String result = getFileName();
99 if (useAppGeneratedBindings == true) {
100 result = tenantName + "-" + result;
106 protected File getTenantsRootDir() {
108 String errMessage = null;
110 String tenantsRootPath = getConfigRootDir() + File.separator
111 + JEEServerDeployment.TENANT_BINDINGS_ROOTDIRNAME;
112 File tenantsRootDir = new File(tenantsRootPath);
113 if (tenantsRootDir.exists() == true) {
114 result = tenantsRootDir;
115 if (logger.isDebugEnabled() == true) {
116 logger.debug("The home directory for all tenants is at: " + result.getCanonicalPath());
119 errMessage = "The home directory for all tenants is missing or inaccessible: ";
121 errMessage = errMessage + tenantsRootDir.getCanonicalPath();
122 } catch (IOException ioException) {
123 errMessage = errMessage + tenantsRootDir.getAbsolutePath();
126 } catch (IOException e) {
127 // Log this exception, but continue anyway. Caller should handle the
128 // null result gracefully.
132 if (errMessage != null) {
133 logger.error(errMessage);
140 * Take the directory of the prototype bindings and the directory of the
141 * delta bindings. Merge the two and create (replace) a file named
142 * "tenant-bindings.xml"
145 private InputStream merge(File srcFile, File deltaFile) throws IOException {
146 InputStream result = null;
148 FileInputStream srcStream = new FileInputStream(srcFile);
149 FileInputStream deltaStream = new FileInputStream(deltaFile);
150 InputStream[] inputStreamArray = { srcStream, deltaStream };
152 Configurer configurer = new AttributeMergeConfigurer();
153 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
154 } catch (Exception e) {
155 logger.error("Could not merge tenant configuration delta file: " + deltaFile.getCanonicalPath(), e);
158 // Try to save the merge output to a file that is suffixed with
159 // ".merged.xml" in the same directory
160 // as the delta file.
162 if (result != null) {
163 File outputDir = deltaFile.getParentFile();
164 String mergedFileName = outputDir.getAbsolutePath() + File.separator
165 + JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
166 File mergedOutFile = new File(mergedFileName);
168 FileUtils.copyInputStreamToFile(result, mergedOutFile); // Save the merge file for debugging
169 } catch (IOException e) {
170 logger.warn("Could not create a copy of the merged tenant configuration at: " + mergedFileName, e);
172 result.reset(); // reset the stream even if the file create failed.
179 public void read(boolean useAppGeneratedBindings) throws Exception {
180 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
181 read(tenantsRootPath, useAppGeneratedBindings);
185 public void read(String tenantRootDirPath, boolean useAppGeneratedBindings) throws Exception {
186 File tenantsRootDir = new File(tenantRootDirPath);
187 if (tenantsRootDir.exists() == false) {
188 throw new Exception("Cound not find tenant bindings root directory: " + tenantRootDirPath);
191 List<File> tenantDirs = getDirectories(tenantsRootDir);
192 tenantBindingTypeList = readTenantConfigs(new File(tenantRootDirPath), tenantDirs, useAppGeneratedBindings);
193 if (tenantBindingTypeList == null || tenantBindingTypeList.size() < 1) {
194 throw new Exception(NO_SERVICE_BINDINGS_FOUND_ERR);
197 for (TenantBindingType tenantBinding : tenantBindingTypeList) {
198 if (allTenantBindings.get(tenantBinding.getId()) != null) {
199 TenantBindingType tenantBindingOld = allTenantBindings.get(tenantBinding.getId());
200 logger.error("Ignoring duplicate binding definition for tenant id=" + tenantBinding.getId()
201 + " existing name=" + tenantBindingOld.getName() + " conflicting (ignored) name="
202 + tenantBinding.getName());
205 allTenantBindings.put(tenantBinding.getId(), tenantBinding);
206 if (!tenantBinding.isCreateDisabled()) {
207 enabledTenantBindings.put(tenantBinding.getId(), tenantBinding);
209 logger.warn(String.format("The tenant '%s':'%s' is marked as disabled in its bindings file.",
210 tenantBinding.getName(), tenantBinding.getId()));
212 readDomains(tenantBinding);
213 readServiceBindings(tenantBinding);
214 if (logger.isInfoEnabled()) {
215 logger.info("Finished reading tenant bindings for tenant id=" + tenantBinding.getId() + " name="
216 + tenantBinding.getName());
217 if (tenantBinding.isCreateDisabled())
218 logger.info("Tenant tenant id={} is marked createDisabled.", tenantBinding.getId());
223 // Ensure that at least one tenant is enabled, otherwise abort the startup.
225 if (enabledTenantBindings.isEmpty() == true) {
226 throw new Exception("All of the configured tenants are marked as disabled in their tenant bindings. At least one tenant needs to be enabled.");
231 * Take the directory of the prototype bindings and the directory of the
232 * delta bindings. Merge the two and create (replace) a file named
233 * "tenant-bindings.xml"
235 * private static String merge(String original, String patch) { InputStream
236 * result = null; try { Configurer configurer = new
237 * AttributeMergeConfigurer();
240 * FileInputStream ins1 = new
241 * FileInputStream(".\\src\\main\\resources\\File1.xml"); FileInputStream
242 * ins2 = new FileInputStream(".\\src\\main\\resources\\File2.xml");
243 * InputStream[] inputStreamArray = {ins1, ins2};
245 * result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray); //
246 * result = new ConfigurableXmlMerge(configurer).merge(new String[]
247 * {original, patch}); } catch (Exception e) { // TODO Auto-generated catch
248 * block e.printStackTrace(); } File mergedOutFile = new
249 * File(".\\target\\merged.xml"); try {
250 * FileUtils.copyInputStreamToFile(result, mergedOutFile); } catch
251 * (IOException e) { // TODO Auto-generated catch block e.printStackTrace();
258 * Merge and read the prototype bindsings with each tenant specific bindings
259 * delta to create the final tenant bindings.
261 * @param protoBindingsFile
262 * - The prototypical bindings file.
263 * @param tenantDirList
264 * - The list of tenant directories containing tenant specific
266 * @return A list of tenant bindings.
267 * @throws IOException
268 * Signals that an I/O exception has occurred.
270 List<TenantBindingType> readTenantConfigs(File protoBindingsDir, List<File> tenantDirList,
271 boolean useAppGeneratedBindings) throws IOException {
272 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
275 // Iterate through a list of directories.
277 for (File tenantDir : tenantDirList) {
278 boolean found = false;
279 String errMessage = null;
281 File tenantBindingsProtoFile = null;
282 String tenantName = tenantDir.getName(); // By convention, the
283 // directory name should
284 // be the tenant name
285 if (useAppGeneratedBindings == true) {
286 tenantBindingsProtoFile = new File(protoBindingsDir.getAbsolutePath() + File.separator + tenantName
287 + "-" + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
289 tenantBindingsProtoFile = new File(protoBindingsDir + File.separator
290 + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
293 if (tenantBindingsProtoFile.exists() == true) {
294 File configFile = new File(tenantDir.getAbsoluteFile() + File.separator
295 + getFileName(tenantName, useAppGeneratedBindings));
296 if (configFile.exists() == true) {
297 InputStream tenantBindingsStream = this.merge(tenantBindingsProtoFile, configFile);
298 TenantBindingConfig tenantBindingConfig = null;
300 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
301 TenantBindingConfig.class);
303 // Compute the MD5 hash of the tenant's binding file. We'll persist this a little later during startup. If the value hasn't
304 // changed since we last startedup, we can skip some of the startup steps.
306 tenantBindingsStream.reset();
307 String md5hash = new String(Hex.encodeHex(DigestUtils.md5(tenantBindingsStream)));
308 tenantBindingConfig.getTenantBinding().setConfigMD5Hash(md5hash); // use this to compare with the last persisted one and to persist as the new hash
309 } catch (Exception e) {
310 logger.error("Could not parse the merged tenant bindings.", e);
312 if (tenantBindingConfig != null) {
313 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
314 if (binding != null) {
317 if (logger.isInfoEnabled() == true) {
318 logger.info("Parsed tenant configuration for: " + binding.getDisplayName());
321 errMessage = "Cound not parse the tenant bindings in: ";
324 errMessage = "Could not parse the tenant bindings file: ";
327 errMessage = "Expected to, but could not, find the tenant delta configuration file: "
328 + configFile.getAbsolutePath();
331 errMessage = "Expected to, but could not, find the tenant proto configuration file: "
332 + tenantBindingsProtoFile.getAbsolutePath();
335 if (found == false) {
336 if (logger.isErrorEnabled() == true) {
337 errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
338 logger.error(errMessage + " - For tenant: " + tenantName);
346 private void readDomains(TenantBindingType tenantBinding) throws Exception {
347 for (RepositoryDomainType domain : tenantBinding.getRepositoryDomain()) {
348 String key = getTenantQualifiedIdentifier(tenantBinding.getId(), domain.getName());
349 domains.put(key, domain);
353 private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
354 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
355 String key = getTenantQualifiedServiceName(tenantBinding.getId(), serviceBinding.getName());
359 serviceBindings.put(key, serviceBinding);
361 if (serviceBinding != null) {
362 ServiceObjectType objectType = serviceBinding.getObject();
363 if (objectType != null) {
364 String docType = objectType.getName();
365 String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType);
366 docTypes.put(docTypeKey, serviceBinding);
369 if (logger.isTraceEnabled()) {
370 logger.trace("readServiceBindings() added service " + " name=" + key + " workspace="
371 + serviceBinding.getName());
377 public List<TenantBindingType> getConfiguration() {
378 return tenantBindingTypeList;
382 * getTenantBindings returns all the tenant bindings read from configuration
386 public Hashtable<String, TenantBindingType> getTenantBindings() {
387 return getTenantBindings(EXCLUDE_CREATE_DISABLED_TENANTS);
391 * getTenantBindings returns all the tenant bindings read from configuration
395 public Hashtable<String, TenantBindingType> getTenantBindings(boolean includeDisabled) {
396 return includeDisabled ? allTenantBindings : enabledTenantBindings;
400 * getTenantBinding gets tenant binding for given tenant
405 public TenantBindingType getTenantBinding(String tenantId) {
406 return allTenantBindings.get(tenantId);
410 * getRepositoryDomain gets repository domain configuration for the given
416 public RepositoryDomainType getRepositoryDomain(String domainName) {
417 return domains.get(domainName.trim());
421 * getRepositoryDomain gets repository domain configuration for the given
422 * service and given tenant id
428 public RepositoryDomainType getRepositoryDomain(String tenantId, String serviceName) {
429 ServiceBindingType serviceBinding = getServiceBinding(tenantId, serviceName);
430 if (serviceBinding == null) {
431 throw new IllegalArgumentException("no service binding found for " + serviceName + " of tenant with id="
434 String repoDomain = serviceBinding.getRepositoryDomain();
435 if (repoDomain == null) {
436 if (logger.isTraceEnabled()) {
437 logger.trace("No repository domain configured for " + serviceName + " of tenant with id=" + tenantId);
441 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
442 return domains.get(key);
446 * getServiceBinding gets service binding for given tenant for a given
453 public ServiceBindingType getServiceBinding(String tenantId, String serviceName) {
454 String key = getTenantQualifiedServiceName(tenantId, serviceName);
455 return serviceBindings.get(key);
459 * getServiceBinding gets service binding for given tenant for a given
466 public ServiceBindingType getServiceBindingForDocType(String tenantId, String docType) {
467 String key = getTenantQualifiedIdentifier(tenantId, ServiceBindingUtils.getUnqualifiedTenantDocType(docType)); // REM - must use unqualified document type
468 return docTypes.get(key);
472 * getServiceBinding gets service binding for given tenant for a given
479 public List<ServiceBindingType> getServiceBindingsByType(String tenantId, String serviceType) {
480 List<String> serviceTypes = new ArrayList<String>(1);
481 serviceTypes.add(serviceType);
482 return getServiceBindingsByType(tenantId, serviceTypes);
486 * getServiceBindingsByType gets service bindings for a given tenant for the
487 * services that fall within a supplied set of service type(s)
490 * @param serviceTypes
493 public List<ServiceBindingType> getServiceBindingsByType(String tenantId, List<String> serviceTypes) {
494 ArrayList<ServiceBindingType> list = null;
495 TenantBindingType tenant = allTenantBindings.get(tenantId);
496 if (tenant != null) {
497 for (ServiceBindingType sb : tenant.getServiceBindings()) {
498 if (serviceTypes.contains(sb.getType())) {
500 list = new ArrayList<ServiceBindingType>();
512 * @return the properly qualified service name
514 public static String getTenantQualifiedServiceName(String tenantId, String serviceName) {
515 String result = null;
517 if (serviceName != null) {
518 logger.trace(String.format(" * tenant:serviceBindings '%s'", serviceName));
519 result = getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
525 public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
526 return tenantId + "." + identifier;
530 * Sets properties in the passed list on the local properties for this
531 * TenantBinding. Note: will only set properties not already set on the
535 * @param propagateToServices
536 * If true, recurses to set set properties on the associated
539 public void setDefaultPropertiesOnTenants(List<PropertyItemType> propList, boolean propagateToServices) {
540 // For each tenant, set properties in list that are not already set
541 if (propList == null || propList.isEmpty()) {
544 for (TenantBindingType tenant : allTenantBindings.values()) {
545 for (PropertyItemType prop : propList) {
546 TenantBindingUtils.setPropertyValue(tenant, prop, TenantBindingUtils.SET_PROP_IF_MISSING);
548 if (propagateToServices) {
549 TenantBindingUtils.propagatePropertiesToServices(tenant, TenantBindingUtils.SET_PROP_IF_MISSING);
554 public String getResourcesDir() {
555 return getConfigRootDir() + File.separator + RESOURCES_DIR_NAME;
559 * Returns a list of tenant identifiers (tenant IDs).
561 * @return a list of tenant IDs
563 public List<String> getTenantIds() {
564 return getTenantIds(EXCLUDE_CREATE_DISABLED_TENANTS);
568 * Returns a list of tenant identifiers (tenant IDs).
570 * @return a list of tenant IDs
572 public List<String> getTenantIds(boolean includeDisabled) {
573 List<String> tenantIds = new ArrayList<String>();
575 Hashtable<String, TenantBindingType> tenantBindings = getTenantBindings(includeDisabled);
576 if (tenantBindings != null && !tenantBindings.isEmpty()) {
577 Enumeration keys = tenantBindings.keys();
578 while (keys.hasMoreElements()) {
579 tenantId = (String) keys.nextElement();
580 if (Tools.notBlank(tenantId)) {
581 tenantIds.add(tenantId);