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.Hashtable;
32 import java.util.List;
34 import org.apache.commons.io.FileUtils;
35 import org.collectionspace.services.common.service.ServiceBindingType;
36 import org.collectionspace.services.common.service.ServiceObjectType;
37 import org.collectionspace.services.common.tenant.RepositoryDomainType;
38 import org.collectionspace.services.common.tenant.TenantBindingType;
39 import org.collectionspace.services.common.tenant.TenantBindingConfig;
40 import org.collectionspace.services.common.types.PropertyItemType;
42 import ch.elca.el4j.util.codingsupport.Reject;
43 import ch.elca.el4j.services.xmlmerge.AbstractXmlMergeException;
44 import ch.elca.el4j.services.xmlmerge.ConfigurationException;
45 import ch.elca.el4j.services.xmlmerge.Configurer;
46 import ch.elca.el4j.services.xmlmerge.XmlMerge;
47 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
48 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
49 import ch.elca.el4j.services.xmlmerge.config.PropertyXPathConfigurer;
50 import ch.elca.el4j.services.xmlmerge.merge.DefaultXmlMerge;
52 import org.apache.commons.io.FileUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
57 import ch.elca.el4j.services.xmlmerge.Configurer;
58 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
59 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
62 * ServicesConfigReader reads service layer specific configuration
64 * $LastChangedRevision: $
67 public class TenantBindingConfigReaderImpl
68 extends AbstractConfigReaderImpl<List<TenantBindingType>> {
69 final private static String TENANT_BINDINGS_ERROR = "Tenant bindings error: ";
70 final private static String TENANT_BINDINGS_ROOTDIRNAME = "tenants";
71 final private static String TENANT_BINDINGS_FILENAME_PREFIX = "tenant-bindings";
72 final private static String TENANT_BINDINGS_PROTOTYPE_FILENAME = TENANT_BINDINGS_FILENAME_PREFIX + "-proto.xml";
73 final private static String TENANT_BINDINGS_DELTA_FILENAME = TENANT_BINDINGS_FILENAME_PREFIX + ".delta.xml";
74 final private static String MERGED_SUFFIX = ".merged.xml";
76 final Logger logger = LoggerFactory.getLogger(TenantBindingConfigReaderImpl.class);
77 private List<TenantBindingType> tenantBindingTypeList;
78 //tenant id, tenant binding
79 private Hashtable<String, TenantBindingType> tenantBindings =
80 new Hashtable<String, TenantBindingType>();
82 private Hashtable<String, RepositoryDomainType> domains =
83 new Hashtable<String, RepositoryDomainType>();
84 //tenant-qualified servicename, service binding
85 private Hashtable<String, ServiceBindingType> serviceBindings =
86 new Hashtable<String, ServiceBindingType>();
88 //tenant-qualified service object name to service name, service binding
89 private Hashtable<String, ServiceBindingType> docTypes =
90 new Hashtable<String, ServiceBindingType>();
93 public TenantBindingConfigReaderImpl(String serverRootDir) {
98 public String getFileName() {
99 return TENANT_BINDINGS_DELTA_FILENAME;
102 protected File getTenantsRootDir() {
104 String tenantsRootPath = getConfigRootDir() + File.separator + TENANT_BINDINGS_ROOTDIRNAME;
105 File tenantsRootDir = new File(tenantsRootPath);
106 if (tenantsRootDir.exists() == true) {
107 result = tenantsRootDir;
108 logger.debug("Tenants home directory is: " + tenantsRootDir.getAbsolutePath()); //FIXME: REM - Add proper if (logger.isDebug() == true) check
110 logger.error("Tenants home directory is missing. Can't find: " + tenantsRootDir.getAbsolutePath()); //FIXME: REM - Add proper if (logger.isError() == true) check
115 * Take the directory of the prototype bindings and the directory of the delta bindings. Merge the two and create (replace) a file
116 * named "tenant-bindings.xml"
119 private InputStream merge(File srcFile, File deltaFile) throws IOException {
120 InputStream result = null;
122 FileInputStream srcStream = new FileInputStream(srcFile);
123 FileInputStream deltaStream = new FileInputStream(deltaFile);
124 InputStream[] inputStreamArray = {srcStream, deltaStream};
126 Configurer configurer = new AttributeMergeConfigurer();
127 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
128 } catch (Exception e) {
129 logger.error("Could not merge tenant configuration delta file: " +
130 deltaFile.getAbsolutePath(), e);
133 // Try to save the merge output to a file that is suffixed with ".merged.xml" in the same directory
134 // as the delta file.
136 if (result != null) {
137 File outputDir = deltaFile.getParentFile();
138 String mergedFileName = outputDir.getAbsolutePath() + File.separator +
139 this.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
140 File mergedOutFile = new File(mergedFileName);
142 FileUtils.copyInputStreamToFile(result, mergedOutFile);
143 } catch (IOException e) {
144 logger.warn("Could not create a copy of the merged tenant configuration at: " +
147 result.reset(); //reset the stream even if the file create failed.
154 public void read() throws Exception {
155 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
156 read(tenantsRootPath);
160 public void read(String tenantRootDirPath) throws Exception {
161 File tenantsRootDir = new File(tenantRootDirPath);
162 if (tenantsRootDir.exists() == false) {
163 throw new Exception("Cound not find tenant bindings root directory: " +
166 File protoBindingsFile = new File(tenantRootDirPath + File.separator +
167 TENANT_BINDINGS_PROTOTYPE_FILENAME);
169 List<File> tenantDirs = getDirectories(tenantsRootDir);
170 tenantBindingTypeList = readTenantConfigs(protoBindingsFile, tenantDirs);
172 for (TenantBindingType tenantBinding : tenantBindingTypeList) {
173 tenantBindings.put(tenantBinding.getId(), tenantBinding);
174 readDomains(tenantBinding);
175 readServiceBindings(tenantBinding);
176 if (logger.isDebugEnabled()) {
177 logger.debug("read() added tenant id=" + tenantBinding.getId()
178 + " name=" + tenantBinding.getName());
184 * Take the directory of the prototype bindings and the directory of the delta bindings. Merge the two and create (replace) a file
185 * named "tenant-bindings.xml"
187 private static String merge(String original, String patch) {
188 InputStream result = null;
190 Configurer configurer = new AttributeMergeConfigurer();
193 FileInputStream ins1 = new FileInputStream(".\\src\\main\\resources\\File1.xml");
194 FileInputStream ins2 = new FileInputStream(".\\src\\main\\resources\\File2.xml");
195 InputStream[] inputStreamArray = {ins1, ins2};
197 result = new ConfigurableXmlMerge(configurer).merge(inputStreamArray);
198 // result = new ConfigurableXmlMerge(configurer).merge(new String[] {original, patch});
199 } catch (Exception e) {
200 // TODO Auto-generated catch block
203 File mergedOutFile = new File(".\\target\\merged.xml");
205 FileUtils.copyInputStreamToFile(result, mergedOutFile);
206 } catch (IOException e) {
207 // TODO Auto-generated catch block
216 * Merge and read the prototype bindsings with each tenant specific bindings delta to create the final
219 * @param protoBindingsFile - The prototypical bindings file.
220 * @param tenantDirList - The list of tenant directories containing tenant specific bindings
221 * @return A list of tenant bindings.
222 * @throws IOException Signals that an I/O exception has occurred.
224 List<TenantBindingType> readTenantConfigs(File protoBindingsFile, List<File> tenantDirList) throws IOException {
225 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
227 // Iterate through a list of directories.
229 for (File tenantDir : tenantDirList) {
230 boolean found = false;
231 String errMessage = null;
232 File configFile = new File(tenantDir.getAbsoluteFile() + File.separator + getFileName());
233 if (configFile.exists() == true) {
234 InputStream tenantBindingsStream = this.merge(protoBindingsFile, configFile);
235 TenantBindingConfig tenantBindingConfig = null;
237 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
238 TenantBindingConfig.class);
239 } catch (Exception e) {
240 logger.error("Could not parse the merged tenant bindings.", e);
242 if (tenantBindingConfig != null) {
243 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
244 if (binding != null) {
247 if (logger.isInfoEnabled() == true) {
248 logger.info("Parsed tenant configureation for: " + binding.getDisplayName());
251 errMessage = "Cound not parse the tentant bindings in: ";
254 errMessage = "Could not parse the tenant bindings file: ";
257 errMessage = "Cound not find a tenant configuration file: ";
259 if (found == false) {
260 if (logger.isErrorEnabled() == true) {
261 errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
262 logger.error(errMessage + configFile.getAbsolutePath());
270 private void readDomains(TenantBindingType tenantBinding) throws Exception {
271 for (RepositoryDomainType domain : tenantBinding.getRepositoryDomain()) {
272 String key = getTenantQualifiedIdentifier(tenantBinding.getId(),
274 domains.put(key, domain);
278 private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
279 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
280 String key = getTenantQualifiedServiceName(tenantBinding.getId(),
281 serviceBinding.getName());
282 serviceBindings.put(key, serviceBinding);
284 if (serviceBinding!=null){
285 ServiceObjectType objectType = serviceBinding.getObject();
286 if (objectType!=null){
287 String docType = objectType.getName();
288 String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType);
289 docTypes.put(docTypeKey, serviceBinding);
292 if (logger.isDebugEnabled()) {
293 logger.debug("readServiceBindings() added service "
295 + " workspace=" + serviceBinding.getName());
301 public List<TenantBindingType> getConfiguration() {
302 return tenantBindingTypeList;
306 * getTenantBindings returns all the tenant bindings read from configuration
309 public Hashtable<String, TenantBindingType> getTenantBindings() {
310 return tenantBindings;
314 * getTenantBinding gets tenant binding for given tenant
318 public TenantBindingType getTenantBinding(
320 return tenantBindings.get(tenantId);
324 * getRepositoryDomain gets repository domain configuration for the given name
328 public RepositoryDomainType getRepositoryDomain(String domainName) {
329 return domains.get(domainName.trim());
333 * getRepositoryDomain gets repository domain configuration for the given service
334 * and given tenant id
339 public RepositoryDomainType getRepositoryDomain(String tenantId, String serviceName) {
340 ServiceBindingType serviceBinding = getServiceBinding(tenantId, serviceName);
341 if (serviceBinding == null) {
342 throw new IllegalArgumentException("no service binding found for " + serviceName
343 + " of tenant with id=" + tenantId);
345 String repoDomain = serviceBinding.getRepositoryDomain();
346 if (repoDomain == null) {
347 /* This is excessive - every call to a JPA based service dumps this msg.
348 if (logger.isDebugEnabled()) {
349 logger.debug("No repository domain configured for " + serviceName
350 + " of tenant with id=" + tenantId);
355 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
356 return domains.get(key);
360 * getServiceBinding gets service binding for given tenant for a given service
365 public ServiceBindingType getServiceBinding(
366 String tenantId, String serviceName) {
367 String key = getTenantQualifiedServiceName(tenantId, serviceName);
368 return serviceBindings.get(key);
372 * getServiceBinding gets service binding for given tenant for a given service
377 public ServiceBindingType getServiceBindingForDocType (String tenantId, String docType) {
378 String key = getTenantQualifiedIdentifier(tenantId, docType);
379 return docTypes.get(key);
383 * getServiceBinding gets service binding for given tenant for a given service
388 public List<ServiceBindingType> getServiceBindingsByType(
389 String tenantId, String serviceType) {
390 ArrayList<ServiceBindingType> list = null;
391 TenantBindingType tenant = tenantBindings.get(tenantId);
392 if (tenant != null) {
393 for (ServiceBindingType sb : tenant.getServiceBindings()) {
394 if (serviceType.equals(sb.getType())) {
396 list = new ArrayList<ServiceBindingType>();
408 * @return the properly qualified service name
410 public static String getTenantQualifiedServiceName(
411 String tenantId, String serviceName) {
412 // return tenantId + "." + serviceName.toLowerCase();
413 return getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
416 public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
417 return tenantId + "." + identifier;
420 * Sets properties in the passed list on the local properties for this TenantBinding.
421 * Note: will only set properties not already set on the TenantBinding.
424 * @param propagateToServices If true, recurses to set set properties
425 * on the associated services.
427 public void setDefaultPropertiesOnTenants(List<PropertyItemType> propList,
428 boolean propagateToServices) {
429 // For each tenant, set properties in list that are not already set
430 if (propList == null || propList.isEmpty()) {
433 for (TenantBindingType tenant : tenantBindings.values()) {
434 for (PropertyItemType prop : propList) {
435 TenantBindingUtils.setPropertyValue(tenant,
436 prop, TenantBindingUtils.SET_PROP_IF_MISSING);
438 if (propagateToServices) {
439 TenantBindingUtils.propagatePropertiesToServices(tenant,
440 TenantBindingUtils.SET_PROP_IF_MISSING);
445 public String getResourcesDir(){
446 return getConfigRootDir() + File.separator + "resources";