]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
cc4fbdc066f1430841e71f83bb4cce93d69a1ce9
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.common.config;
25
26 import java.io.File;
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;
37
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;
50
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;
54
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * ServicesConfigReader reads service layer specific configuration
60  * 
61  * $LastChangedRevision: $ $LastChangedDate: $
62  */
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;
66
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
69                         + ".delta.xml";
70         final private static String MERGED_SUFFIX = ".merged.xml";
71         private static final String NO_SERVICE_BINDINGS_FOUND_ERR = "No Service bindings found.";
72
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>();
79         // repository domains
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>();
83
84         // tenant-qualified service object name to service name, service binding
85         private Hashtable<String, ServiceBindingType> docTypes = new Hashtable<String, ServiceBindingType>();
86
87         public TenantBindingConfigReaderImpl(String serverRootDir) {
88                 super(serverRootDir);
89         }
90
91         @Override
92         public String getFileName() {
93                 return TENANT_BINDINGS_DELTA_FILENAME;
94         }
95
96         private String getFileName(String tenantName, boolean useAppGeneratedBindings) {
97                 String result = getFileName();
98
99                 if (useAppGeneratedBindings == true) {
100                         result = tenantName + "-" + result;
101                 }
102
103                 return result;
104         }
105
106         protected File getTenantsRootDir() {
107                 File result = null;
108                 String errMessage = null;
109                 try {
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());
117                                 }
118                         } else {
119                                 errMessage = "The home directory for all tenants is missing or inaccessible: ";
120                                 try {
121                                         errMessage = errMessage + tenantsRootDir.getCanonicalPath();
122                                 } catch (IOException ioException) {
123                                         errMessage = errMessage + tenantsRootDir.getAbsolutePath();
124                                 }
125                         }
126                 } catch (IOException e) {
127                         // Log this exception, but continue anyway. Caller should handle the
128                         // null result gracefully.
129                         logger.equals(e);
130                 }
131
132                 if (errMessage != null) {
133                         logger.error(errMessage);
134                 }
135
136                 return result;
137         }
138
139         /*
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"
143          */
144
145         private InputStream merge(File srcFile, File deltaFile) throws IOException {
146                 InputStream result = null;
147                 try {
148                         FileInputStream srcStream = new FileInputStream(srcFile);
149                         FileInputStream deltaStream = new FileInputStream(deltaFile);
150                         InputStream[] inputStreamArray = { srcStream, deltaStream };
151
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);
156                 }
157                 //
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.
161                 //
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);
167                         try {
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);
171                         }
172                         result.reset(); // reset the stream even if the file create failed.
173                 }
174
175                 return result;
176         }
177
178         @Override
179         public void read(boolean useAppGeneratedBindings) throws Exception {
180                 String tenantsRootPath = getTenantsRootDir().getAbsolutePath();
181                 read(tenantsRootPath, useAppGeneratedBindings);
182         }
183
184         @Override
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);
189                 }
190
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);
195                 }
196
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());
203                                 continue;
204                         }
205                         allTenantBindings.put(tenantBinding.getId(), tenantBinding);
206                         if (!tenantBinding.isCreateDisabled()) {
207                                 enabledTenantBindings.put(tenantBinding.getId(), tenantBinding);
208                         } else {
209                                 logger.warn(String.format("The tenant '%s':'%s' is marked as disabled in its bindings file.",
210                                                 tenantBinding.getName(), tenantBinding.getId()));
211                         }
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());
219                         }
220                 }
221                 
222                 //
223                 // Ensure that at least one tenant is enabled, otherwise abort the startup.
224                 //
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.");
227                 }
228         }
229
230         /*
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"
234          * 
235          * private static String merge(String original, String patch) { InputStream
236          * result = null; try { Configurer configurer = new
237          * AttributeMergeConfigurer();
238          * 
239          * 
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};
244          * 
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();
252          * }
253          * 
254          * return null; }
255          */
256
257         /**
258          * Merge and read the prototype bindsings with each tenant specific bindings
259          * delta to create the final tenant bindings.
260          * 
261          * @param protoBindingsFile
262          *            - The prototypical bindings file.
263          * @param tenantDirList
264          *            - The list of tenant directories containing tenant specific
265          *            bindings
266          * @return A list of tenant bindings.
267          * @throws IOException
268          *             Signals that an I/O exception has occurred.
269          */
270         List<TenantBindingType> readTenantConfigs(File protoBindingsDir, List<File> tenantDirList,
271                         boolean useAppGeneratedBindings) throws IOException {
272                 List<TenantBindingType> result = new ArrayList<TenantBindingType>();
273
274                 //
275                 // Iterate through a list of directories.
276                 //
277                 for (File tenantDir : tenantDirList) {
278                         boolean found = false;
279                         String errMessage = null;
280
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);
288                         } else {
289                                 tenantBindingsProtoFile = new File(protoBindingsDir + File.separator
290                                                 + JEEServerDeployment.TENANT_BINDINGS_PROTOTYPE_FILENAME);
291                         }
292
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;
299                                         try {
300                                                 tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
301                                                                 TenantBindingConfig.class);
302                                                 //
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.
305                                                 //
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);
311                                         }
312                                         if (tenantBindingConfig != null) {
313                                                 TenantBindingType binding = tenantBindingConfig.getTenantBinding();
314                                                 if (binding != null) {
315                                                         result.add(binding);
316                                                         found = true;
317                                                         if (logger.isInfoEnabled() == true) {
318                                                                 logger.info("Parsed tenant configuration for: " + binding.getDisplayName());
319                                                         }
320                                                 } else {
321                                                         errMessage = "Cound not parse the tenant bindings in: ";
322                                                 }
323                                         } else {
324                                                 errMessage = "Could not parse the tenant bindings file: ";
325                                         }
326                                 } else {
327                                         errMessage = "Expected to, but could not, find the tenant delta configuration file: "
328                                                         + configFile.getAbsolutePath();
329                                 }
330                         } else {
331                                 errMessage = "Expected to, but could not, find the tenant proto configuration file: "
332                                                 + tenantBindingsProtoFile.getAbsolutePath();
333                         }
334
335                         if (found == false) {
336                                 if (logger.isErrorEnabled() == true) {
337                                         errMessage = errMessage != null ? errMessage : TENANT_BINDINGS_ERROR;
338                                         logger.error(errMessage + " - For tenant: " + tenantName);
339                                 }
340                         }
341                 } // else-for
342
343                 return result;
344         }
345
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);
350                 }
351         }
352
353         private void readServiceBindings(TenantBindingType tenantBinding) throws Exception {
354                 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
355                         String key = getTenantQualifiedServiceName(tenantBinding.getId(), serviceBinding.getName());
356                         if (key == null) {
357                                 continue;
358                         }
359                         serviceBindings.put(key, serviceBinding);
360
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);
367                                 }
368                         }
369                         if (logger.isTraceEnabled()) {
370                                 logger.trace("readServiceBindings() added service " + " name=" + key + " workspace="
371                                                 + serviceBinding.getName());
372                         }
373                 }
374         }
375
376         @Override
377         public List<TenantBindingType> getConfiguration() {
378                 return tenantBindingTypeList;
379         }
380
381         /**
382          * getTenantBindings returns all the tenant bindings read from configuration
383          * 
384          * @return
385          */
386         public Hashtable<String, TenantBindingType> getTenantBindings() {
387                 return getTenantBindings(EXCLUDE_CREATE_DISABLED_TENANTS);
388         }
389
390         /**
391          * getTenantBindings returns all the tenant bindings read from configuration
392          * 
393          * @return
394          */
395         public Hashtable<String, TenantBindingType> getTenantBindings(boolean includeDisabled) {
396                 return includeDisabled ? allTenantBindings : enabledTenantBindings;
397         }
398
399         /**
400          * getTenantBinding gets tenant binding for given tenant
401          * 
402          * @param tenantId
403          * @return
404          */
405         public TenantBindingType getTenantBinding(String tenantId) {
406                 return allTenantBindings.get(tenantId);
407         }
408
409         /**
410          * getRepositoryDomain gets repository domain configuration for the given
411          * name
412          * 
413          * @param domainName
414          * @return
415          */
416         public RepositoryDomainType getRepositoryDomain(String domainName) {
417                 return domains.get(domainName.trim());
418         }
419
420         /**
421          * getRepositoryDomain gets repository domain configuration for the given
422          * service and given tenant id
423          * 
424          * @param tenantId
425          * @param serviceName
426          * @return
427          */
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="
432                                         + tenantId);
433                 }
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);
438                         }
439                         return null;
440                 }
441                 String key = this.getTenantQualifiedIdentifier(tenantId, repoDomain.trim());
442                 return domains.get(key);
443         }
444
445         /**
446          * getServiceBinding gets service binding for given tenant for a given
447          * service
448          * 
449          * @param tenantId
450          * @param serviceName
451          * @return
452          */
453         public ServiceBindingType getServiceBinding(String tenantId, String serviceName) {
454                 String key = getTenantQualifiedServiceName(tenantId, serviceName);
455                 return serviceBindings.get(key);
456         }
457
458         /**
459          * getServiceBinding gets service binding for given tenant for a given
460          * service
461          * 
462          * @param tenantId
463          * @param docType
464          * @return
465          */
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);
469         }
470
471         /**
472          * getServiceBinding gets service binding for given tenant for a given
473          * service
474          * 
475          * @param tenantId
476          * @param serviceName
477          * @return
478          */
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);
483         }
484
485         /**
486          * getServiceBindingsByType gets service bindings for a given tenant for the
487          * services that fall within a supplied set of service type(s)
488          * 
489          * @param tenantId
490          * @param serviceTypes
491          * @return
492          */
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())) {
499                                         if (list == null) {
500                                                 list = new ArrayList<ServiceBindingType>();
501                                         }
502                                         list.add(sb);
503                                 }
504                         }
505                 }
506                 return list;
507         }
508
509         /**
510          * @param tenantId
511          * @param serviceName
512          * @return the properly qualified service name
513          */
514         public static String getTenantQualifiedServiceName(String tenantId, String serviceName) {
515                 String result = null;
516
517                 if (serviceName != null) {
518                         logger.trace(String.format(" * tenant:serviceBindings '%s'", serviceName));
519                         result = getTenantQualifiedIdentifier(tenantId, serviceName.toLowerCase());
520                 }
521
522                 return result;
523         }
524
525         public static String getTenantQualifiedIdentifier(String tenantId, String identifier) {
526                 return tenantId + "." + identifier;
527         }
528
529         /**
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
532          * TenantBinding.
533          * 
534          * @param propList
535          * @param propagateToServices
536          *            If true, recurses to set set properties on the associated
537          *            services.
538          */
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()) {
542                         return;
543                 }
544                 for (TenantBindingType tenant : allTenantBindings.values()) {
545                         for (PropertyItemType prop : propList) {
546                                 TenantBindingUtils.setPropertyValue(tenant, prop, TenantBindingUtils.SET_PROP_IF_MISSING);
547                         }
548                         if (propagateToServices) {
549                                 TenantBindingUtils.propagatePropertiesToServices(tenant, TenantBindingUtils.SET_PROP_IF_MISSING);
550                         }
551                 }
552         }
553
554         public String getResourcesDir() {
555                 return getConfigRootDir() + File.separator + RESOURCES_DIR_NAME;
556         }
557
558         /**
559          * Returns a list of tenant identifiers (tenant IDs).
560          * 
561          * @return a list of tenant IDs
562          */
563         public List<String> getTenantIds() {
564                 return getTenantIds(EXCLUDE_CREATE_DISABLED_TENANTS);
565         }
566
567         /**
568          * Returns a list of tenant identifiers (tenant IDs).
569          * 
570          * @return a list of tenant IDs
571          */
572         public List<String> getTenantIds(boolean includeDisabled) {
573                 List<String> tenantIds = new ArrayList<String>();
574                 String tenantId;
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);
582                                 }
583                         }
584                 }
585                 return tenantIds;
586         }
587 }