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.util.ArrayList;
31 import java.util.Enumeration;
32 import java.util.Hashtable;
33 import java.util.List;
35 import org.apache.commons.io.FileUtils;
36 import org.collectionspace.services.common.ServiceMain;
37 import org.collectionspace.services.common.api.JEEServerDeployment;
38 import org.collectionspace.services.common.api.Tools;
39 import org.collectionspace.services.config.service.ServiceBindingType;
40 import org.collectionspace.services.config.service.ServiceObjectType;
41 import org.collectionspace.services.config.tenant.RepositoryDomainType;
42 import org.collectionspace.services.config.tenant.TenantBindingConfig;
43 import org.collectionspace.services.config.tenant.TenantBindingType;
44 import org.collectionspace.services.config.types.PropertyItemType;
46 import ch.elca.el4j.services.xmlmerge.Configurer;
47 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
48 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * ServicesConfigReader reads service layer specific configuration
56 * $LastChangedRevision: $ $LastChangedDate: $
58 public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl<List<TenantBindingType>> {
59 final public static boolean INCLUDE_CREATE_DISABLED_TENANTS = true;
60 final public static boolean EXCLUDE_CREATE_DISABLED_TENANTS = false;
62 final private static String TENANT_BINDINGS_ERROR = "Tenant bindings error(s) for tenant: ";
63 final private static String TENANT_BINDINGS_DELTA_FILENAME = JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX
65 final private static String MERGED_SUFFIX = ".merged.xml";
66 private static final String NO_SERVICE_BINDINGS_FOUND_ERR = "No Service bindings found.";
68 private static final Logger logger = LoggerFactory.getLogger(TenantBindingConfigReaderImpl.class);
69 private List<TenantBindingType> tenantBindingTypeList;
70 // tenant id, tenant binding, for tenants not marked as createDisabled
71 private Hashtable<String, TenantBindingType> enabledTenantBindings = new Hashtable<String, TenantBindingType>();
72 // tenant id, tenant binding
73 private Hashtable<String, TenantBindingType> allTenantBindings = new Hashtable<String, TenantBindingType>();
75 private Hashtable<String, RepositoryDomainType> domains = new Hashtable<String, RepositoryDomainType>();
76 // tenant-qualified servicename, service binding
77 private Hashtable<String, ServiceBindingType> serviceBindings = new Hashtable<String, ServiceBindingType>();
79 // tenant-qualified service object name to service name, service binding
80 private Hashtable<String, ServiceBindingType> docTypes = new Hashtable<String, ServiceBindingType>();
82 public TenantBindingConfigReaderImpl(String serverRootDir) {
87 public String getFileName() {
88 return TENANT_BINDINGS_DELTA_FILENAME;
91 private String getFileName(String tenantName, boolean useAppGeneratedBindings) {
92 String result = getFileName();
94 if (useAppGeneratedBindings == true) {
95 result = tenantName + "-" + result;
101 protected File getTenantsRootDir() {
103 String errMessage = null;
105 String tenantsRootPath = getConfigRootDir() + File.separator
106 + JEEServerDeployment.TENANT_BINDINGS_ROOTDIRNAME;
107 File tenantsRootDir = new File(tenantsRootPath);
108 if (tenantsRootDir.exists() == true) {
109 result = tenantsRootDir;
110 if (logger.isDebugEnabled() == true) {
111 logger.debug("The home directory for all tenants is at: " + result.getCanonicalPath());
114 errMessage = "The home directory for all tenants is missing or inaccessible: ";
116 errMessage = errMessage + tenantsRootDir.getCanonicalPath();
117 } catch (IOException ioException) {
118 errMessage = errMessage + tenantsRootDir.getAbsolutePath();
121 } catch (IOException e) {
122 // Log this exception, but continue anyway. Caller should handle the
123 // null result gracefully.
127 if (errMessage != null) {
128 logger.error(errMessage);
135 * Take the directory of the prototype bindings and the directory of the
136 * delta bindings. Merge the two and create (replace) a file named
137 * "tenant-bindings.xml"
140 private InputStream merge(File srcFile, File deltaFile) throws IOException {
141 InputStream result = null;
143 FileInputStream srcStream = new FileInputStream(srcFile);
144 FileInputStream deltaStream = new FileInputStream(deltaFile);
145 InputStream[] inputStreamArray = { srcStream, deltaStream };
147 Configurer configurer = new AttributeMergeConfigurer();
148 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
149 } catch (Exception e) {
150 logger.error("Could not merge tenant configuration delta file: " + deltaFile.getCanonicalPath(), e);
153 // Try to save the merge output to a file that is suffixed with
154 // ".merged.xml" in the same directory
155 // as the delta file.
157 if (result != null) {
158 File outputDir = deltaFile.getParentFile();
159 String mergedFileName = outputDir.getAbsolutePath() + File.separator
160 + JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
161 File mergedOutFile = new File(mergedFileName);
163 FileUtils.copyInputStreamToFile(result, mergedOutFile);
164 } catch (IOException e) {
165 logger.warn("Could not create a copy of the merged tenant configuration at: " + mergedFileName, e);
167 result.reset(); // reset the stream even if the file create failed.
174 public void read(boolean useAppGeneratedBindings) throws Exception {
175 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
176 read(tenantsRootPath, useAppGeneratedBindings);
180 public void read(String tenantRootDirPath, boolean useAppGeneratedBindings) throws Exception {
181 File tenantsRootDir = new File(tenantRootDirPath);
182 if (tenantsRootDir.exists() == false) {
183 throw new Exception("Cound not find tenant bindings root directory: " + tenantRootDirPath);
186 List<File> tenantDirs = getDirectories(tenantsRootDir);
187 tenantBindingTypeList = readTenantConfigs(new File(tenantRootDirPath), tenantDirs, useAppGeneratedBindings);
188 if (tenantBindingTypeList == null || tenantBindingTypeList.size() < 1) {
189 throw new Exception(NO_SERVICE_BINDINGS_FOUND_ERR);
192 for (TenantBindingType tenantBinding : tenantBindingTypeList) {
193 if (allTenantBindings.get(tenantBinding.getId()) != null) {
194 TenantBindingType tenantBindingOld = allTenantBindings.get(tenantBinding.getId());
195 logger.error("Ignoring duplicate binding definition for tenant id=" + tenantBinding.getId()
196 + " existing name=" + tenantBindingOld.getName() + " conflicting (ignored) name="
197 + tenantBinding.getName());
200 allTenantBindings.put(tenantBinding.getId(), tenantBinding);
201 if (!tenantBinding.isCreateDisabled()) {
202 enabledTenantBindings.put(tenantBinding.getId(), tenantBinding);
205 readDomains(tenantBinding);
206 readServiceBindings(tenantBinding);
207 if (logger.isInfoEnabled()) {
208 logger.info("Finished reading tenant bindings for tenant id=" + tenantBinding.getId() + " name="
209 + tenantBinding.getName());
210 if (tenantBinding.isCreateDisabled())
211 logger.info("Tenant tenant id={} is marked createDisabled.", tenantBinding.getId());
217 * Take the directory of the prototype bindings and the directory of the
218 * delta bindings. Merge the two and create (replace) a file named
219 * "tenant-bindings.xml"
221 * private static String merge(String original, String patch) { InputStream
222 * result = null; try { Configurer configurer = new
223 * AttributeMergeConfigurer();
226 * FileInputStream ins1 = new
227 * FileInputStream(".\\src\\main\\resources\\File1.xml"); FileInputStream
228 * ins2 = new FileInputStream(".\\src\\main\\resources\\File2.xml");
229 * InputStream[] inputStreamArray = {ins1, ins2};
231 * result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray); //
232 * result = new ConfigurableXmlMerge(configurer).merge(new String[]
233 * {original, patch}); } catch (Exception e) { // TODO Auto-generated catch
234 * block e.printStackTrace(); } File mergedOutFile = new
235 * File(".\\target\\merged.xml"); try {
236 * FileUtils.copyInputStreamToFile(result, mergedOutFile); } catch
237 * (IOException e) { // TODO Auto-generated catch block e.printStackTrace();
244 * Merge and read the prototype bindsings with each tenant specific bindings
245 * delta to create the final tenant bindings.
247 * @param protoBindingsFile
248 * - The prototypical bindings file.
249 * @param tenantDirList
250 * - The list of tenant directories containing tenant specific
252 * @return A list of tenant bindings.
253 * @throws IOException
254 * Signals that an I/O exception has occurred.
256 List<TenantBindingType> readTenantConfigs(File protoBindingsDir, List<File> tenantDirList,
257 boolean useAppGeneratedBindings) throws IOException {
258 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
261 // Iterate through a list of directories.
263 for (File tenantDir : tenantDirList) {
264 boolean found = false;
265 String errMessage = null;
267 File tenantBindingsProtoFile = null;
268 String tenantName = tenantDir.getName(); // By convention, the
269 // directory name should
270 // be the tenant name
271 if (useAppGeneratedBindings == true) {
272 tenantBindingsProtoFile = new File(protoBindingsDir.getAbsolutePath() + File.separator + tenantName
273 + "-" + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
275 tenantBindingsProtoFile = new File(protoBindingsDir + File.separator
276 + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
279 if (tenantBindingsProtoFile.exists() == true) {
280 File configFile = new File(tenantDir.getAbsoluteFile() + File.separator
281 + getFileName(tenantName, useAppGeneratedBindings));
282 if (configFile.exists() == true) {
283 InputStream tenantBindingsStream = this.merge(tenantBindingsProtoFile, configFile);
284 TenantBindingConfig tenantBindingConfig = null;
286 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
287 TenantBindingConfig.class);
288 } catch (Exception e) {
289 logger.error("Could not parse the merged tenant bindings.", e);
291 if (tenantBindingConfig != null) {
292 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
293 if (binding != null) {
296 if (logger.isInfoEnabled() == true) {
297 logger.info("Parsed tenant configuration for: " + binding.getDisplayName());
300 errMessage = "Cound not parse the tentant bindings in: ";
303 errMessage = "Could not parse the tenant bindings file: ";
306 errMessage = "Expected to, but could not, find the tenant delta configuration file: "
307 + configFile.getAbsolutePath();
310 errMessage = "Expected to, but could not, find the tenant proto configuration file: "
311 + tenantBindingsProtoFile.getAbsolutePath();
314 if (found == false) {
315 if (logger.isErrorEnabled() == true) {
316 errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
317 logger.error(errMessage + " - For tenant: " + tenantName);
325 private void readDomains(TenantBindingType tenantBinding) throws Exception {
326 for (RepositoryDomainType domain : tenantBinding.getRepositoryDomain()) {
327 String key = getTenantQualifiedIdentifier(tenantBinding.getId(), domain.getName());
328 domains.put(key, domain);
332 private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
333 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
334 String key = getTenantQualifiedServiceName(tenantBinding.getId(), serviceBinding.getName());
338 serviceBindings.put(key, serviceBinding);
340 if (serviceBinding != null) {
341 ServiceObjectType objectType = serviceBinding.getObject();
342 if (objectType != null) {
343 String docType = objectType.getName();
344 String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType);
345 docTypes.put(docTypeKey, serviceBinding);
348 if (logger.isTraceEnabled()) {
349 logger.trace("readServiceBindings() added service " + " name=" + key + " workspace="
350 + serviceBinding.getName());
356 public List<TenantBindingType> getConfiguration() {
357 return tenantBindingTypeList;
361 * getTenantBindings returns all the tenant bindings read from configuration
365 public Hashtable<String, TenantBindingType> getTenantBindings() {
366 return getTenantBindings(EXCLUDE_CREATE_DISABLED_TENANTS);
370 * getTenantBindings returns all the tenant bindings read from configuration
374 public Hashtable<String, TenantBindingType> getTenantBindings(boolean includeDisabled) {
375 return includeDisabled ? allTenantBindings : enabledTenantBindings;
379 * getTenantBinding gets tenant binding for given tenant
384 public TenantBindingType getTenantBinding(String tenantId) {
385 return allTenantBindings.get(tenantId);
389 * getRepositoryDomain gets repository domain configuration for the given
395 public RepositoryDomainType getRepositoryDomain(String domainName) {
396 return domains.get(domainName.trim());
400 * getRepositoryDomain gets repository domain configuration for the given
401 * service and given tenant id
407 public RepositoryDomainType getRepositoryDomain(String tenantId, String serviceName) {
408 ServiceBindingType serviceBinding = getServiceBinding(tenantId, serviceName);
409 if (serviceBinding == null) {
410 throw new IllegalArgumentException("no service binding found for " + serviceName + " of tenant with id="
413 String repoDomain = serviceBinding.getRepositoryDomain();
414 if (repoDomain == null) {
415 if (logger.isTraceEnabled()) {
416 logger.trace("No repository domain configured for " + serviceName + " of tenant with id=" + tenantId);
420 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
421 return domains.get(key);
425 * getServiceBinding gets service binding for given tenant for a given
432 public ServiceBindingType getServiceBinding(String tenantId, String serviceName) {
433 String key = getTenantQualifiedServiceName(tenantId, serviceName);
434 return serviceBindings.get(key);
438 * getServiceBinding gets service binding for given tenant for a given
445 public ServiceBindingType getServiceBindingForDocType(String tenantId, String docType) {
446 String key = getTenantQualifiedIdentifier(tenantId, docType);
447 return docTypes.get(key);
451 * getServiceBinding gets service binding for given tenant for a given
458 public List<ServiceBindingType> getServiceBindingsByType(String tenantId, String serviceType) {
459 List<String> serviceTypes = new ArrayList<String>(1);
460 serviceTypes.add(serviceType);
461 return getServiceBindingsByType(tenantId, serviceTypes);
465 * getServiceBindingsByType gets service bindings for a given tenant for the
466 * services that fall within a supplied set of service type(s)
469 * @param serviceTypes
472 public List<ServiceBindingType> getServiceBindingsByType(String tenantId, List<String> serviceTypes) {
473 ArrayList<ServiceBindingType> list = null;
474 TenantBindingType tenant = allTenantBindings.get(tenantId);
475 if (tenant != null) {
476 for (ServiceBindingType sb : tenant.getServiceBindings()) {
477 if (serviceTypes.contains(sb.getType())) {
479 list = new ArrayList<ServiceBindingType>();
491 * @return the properly qualified service name
493 public static String getTenantQualifiedServiceName(String tenantId, String serviceName) {
494 String result = null;
496 if (serviceName != null) {
497 if (logger.isDebugEnabled() == true) {
498 logger.debug(String.format(" * tenant:serviceBindings '%s'", serviceName));
499 System.out.println(String.format(" * tenant:serviceBindings '%s'", serviceName)); // Debug only
501 result = getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
507 public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
508 return tenantId + "." + identifier;
512 * Sets properties in the passed list on the local properties for this
513 * TenantBinding. Note: will only set properties not already set on the
517 * @param propagateToServices
518 * If true, recurses to set set properties on the associated
521 public void setDefaultPropertiesOnTenants(List<PropertyItemType> propList, boolean propagateToServices) {
522 // For each tenant, set properties in list that are not already set
523 if (propList == null || propList.isEmpty()) {
526 for (TenantBindingType tenant : allTenantBindings.values()) {
527 for (PropertyItemType prop : propList) {
528 TenantBindingUtils.setPropertyValue(tenant, prop, TenantBindingUtils.SET_PROP_IF_MISSING);
530 if (propagateToServices) {
531 TenantBindingUtils.propagatePropertiesToServices(tenant, TenantBindingUtils.SET_PROP_IF_MISSING);
536 public String getResourcesDir() {
537 return getConfigRootDir() + File.separator + RESOURCES_DIR_NAME;
541 * Returns a list of tenant identifiers (tenant IDs).
543 * @return a list of tenant IDs
545 public List<String> getTenantIds() {
546 return getTenantIds(EXCLUDE_CREATE_DISABLED_TENANTS);
550 * Returns a list of tenant identifiers (tenant IDs).
552 * @return a list of tenant IDs
554 public List<String> getTenantIds(boolean includeDisabled) {
555 List<String> tenantIds = new ArrayList<String>();
557 Hashtable<String, TenantBindingType> tenantBindings = getTenantBindings(includeDisabled);
558 if (tenantBindings != null && !tenantBindings.isEmpty()) {
559 Enumeration keys = tenantBindings.keys();
560 while (keys.hasMoreElements()) {
561 tenantId = (String) keys.nextElement();
562 if (Tools.notBlank(tenantId)) {
563 tenantIds.add(tenantId);