View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.logic.init;
20  
21  import java.lang.reflect.Modifier;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Objects;
28  import java.util.Set;
29  import java.util.stream.Collectors;
30  import org.apache.syncope.common.lib.policy.AccountRuleConf;
31  import org.apache.syncope.common.lib.policy.PasswordRuleConf;
32  import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
33  import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
34  import org.apache.syncope.common.lib.report.ReportConf;
35  import org.apache.syncope.common.lib.types.IdMImplementationType;
36  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
37  import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
38  import org.apache.syncope.core.logic.api.Command;
39  import org.apache.syncope.core.logic.api.LogicActions;
40  import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
41  import org.apache.syncope.core.provisioning.api.ImplementationLookup;
42  import org.apache.syncope.core.provisioning.api.ProvisionSorter;
43  import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
44  import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
45  import org.apache.syncope.core.provisioning.api.job.report.ReportConfClass;
46  import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
47  import org.apache.syncope.core.provisioning.api.notification.RecipientsProvider;
48  import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
49  import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
50  import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
51  import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
52  import org.apache.syncope.core.provisioning.api.rules.AccountRule;
53  import org.apache.syncope.core.provisioning.api.rules.AccountRuleConfClass;
54  import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
55  import org.apache.syncope.core.provisioning.api.rules.PasswordRuleConfClass;
56  import org.apache.syncope.core.provisioning.api.rules.PullCorrelationRule;
57  import org.apache.syncope.core.provisioning.api.rules.PullCorrelationRuleConfClass;
58  import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
59  import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRuleConfClass;
60  import org.apache.syncope.core.provisioning.java.data.JEXLItemTransformerImpl;
61  import org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate;
62  import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
63  import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  import org.springframework.beans.factory.config.BeanDefinition;
67  import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
68  import org.springframework.core.Ordered;
69  import org.springframework.core.type.filter.AssignableTypeFilter;
70  import org.springframework.util.ClassUtils;
71  
72  /**
73   * Cache class names for all implementations of Syncope interfaces found in classpath, for later usage.
74   */
75  public class ClassPathScanImplementationLookup implements ImplementationLookup {
76  
77      private static final Logger LOG = LoggerFactory.getLogger(ImplementationLookup.class);
78  
79      private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.core";
80  
81      private Map<String, Set<String>> classNames;
82  
83      private Map<Class<? extends ReportConf>, Class<? extends ReportJobDelegate>> reportJobDelegateClasses;
84  
85      private Map<Class<? extends AccountRuleConf>, Class<? extends AccountRule>> accountRuleClasses;
86  
87      private Map<Class<? extends PasswordRuleConf>, Class<? extends PasswordRule>> passwordRuleClasses;
88  
89      private Map<Class<? extends PullCorrelationRuleConf>, Class<? extends PullCorrelationRule>> pullCRClasses;
90  
91      private Map<Class<? extends PushCorrelationRuleConf>, Class<? extends PushCorrelationRule>> pushCRClasses;
92  
93      @Override
94      public int getOrder() {
95          return Ordered.HIGHEST_PRECEDENCE + 1;
96      }
97  
98      /**
99       * This method can be overridden by subclasses to customize classpath scan.
100      *
101      * @return basePackage for classpath scanning
102      */
103     protected static String getBasePackage() {
104         return DEFAULT_BASE_PACKAGE;
105     }
106 
107     @SuppressWarnings("unchecked")
108     @Override
109     public void load() {
110         classNames = new HashMap<>();
111         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
112         ImplementationTypesHolder.getInstance().getValues().forEach((typeName, typeInterface) -> {
113             classNames.put(typeName, new HashSet<>());
114             try {
115                 scanner.addIncludeFilter(new AssignableTypeFilter(
116                         ClassUtils.resolveClassName(typeInterface, ClassUtils.getDefaultClassLoader())));
117             } catch (IllegalArgumentException e) {
118                 LOG.error("Could not find class, ignoring...", e);
119             }
120         });
121 
122         Map<String, String> extImplTypes = ImplementationTypesHolder.getInstance().getValues().entrySet().stream().
123                 filter(e -> !IdRepoImplementationType.values().containsKey(e.getKey())
124                 && !IdMImplementationType.values().containsKey(e.getKey())).
125                 collect(Collectors.toMap(Entry::getKey, Entry::getValue));
126 
127         reportJobDelegateClasses = new HashMap<>();
128         accountRuleClasses = new HashMap<>();
129         passwordRuleClasses = new HashMap<>();
130         pullCRClasses = new HashMap<>();
131         pushCRClasses = new HashMap<>();
132 
133         for (BeanDefinition bd : scanner.findCandidateComponents(getBasePackage())) {
134             try {
135                 Class<?> clazz = ClassUtils.resolveClassName(
136                         Objects.requireNonNull(bd.getBeanClassName()), ClassUtils.getDefaultClassLoader());
137                 if (Modifier.isAbstract(clazz.getModifiers())) {
138                     continue;
139                 }
140 
141                 if (ReportJobDelegate.class.isAssignableFrom(clazz)) {
142                     ReportConfClass annotation = clazz.getAnnotation(ReportConfClass.class);
143                     if (annotation == null) {
144                         LOG.warn("Found Report {} without declared configuration", clazz.getName());
145                     } else {
146                         classNames.get(IdRepoImplementationType.REPORT_DELEGATE).add(clazz.getName());
147                         reportJobDelegateClasses.put(annotation.value(), (Class<? extends ReportJobDelegate>) clazz);
148                     }
149                 } else if (AccountRule.class.isAssignableFrom(clazz)) {
150                     AccountRuleConfClass annotation = clazz.getAnnotation(AccountRuleConfClass.class);
151                     if (annotation == null) {
152                         LOG.warn("Found account policy rule {} without declared configuration", clazz.getName());
153                     } else {
154                         classNames.get(IdRepoImplementationType.ACCOUNT_RULE).add(clazz.getName());
155                         accountRuleClasses.put(annotation.value(), (Class<? extends AccountRule>) clazz);
156                     }
157                 } else if (PasswordRule.class.isAssignableFrom(clazz)) {
158                     PasswordRuleConfClass annotation = clazz.getAnnotation(PasswordRuleConfClass.class);
159                     if (annotation == null) {
160                         LOG.warn("Found password policy rule {} without declared configuration", clazz.getName());
161                     } else {
162                         classNames.get(IdRepoImplementationType.PASSWORD_RULE).add(clazz.getName());
163                         passwordRuleClasses.put(annotation.value(), (Class<? extends PasswordRule>) clazz);
164                     }
165                 } else if (PullCorrelationRule.class.isAssignableFrom(clazz)) {
166                     PullCorrelationRuleConfClass annotation = clazz.getAnnotation(PullCorrelationRuleConfClass.class);
167                     if (annotation == null) {
168                         LOG.warn("Found pull correlation rule {} without declared configuration", clazz.getName());
169                     } else {
170                         classNames.get(IdMImplementationType.PULL_CORRELATION_RULE).add(clazz.getName());
171                         pullCRClasses.put(annotation.value(), (Class<? extends PullCorrelationRule>) clazz);
172                     }
173                 } else if (PushCorrelationRule.class.isAssignableFrom(clazz)) {
174                     PushCorrelationRuleConfClass annotation = clazz.getAnnotation(PushCorrelationRuleConfClass.class);
175                     if (annotation == null) {
176                         LOG.warn("Found push correlation rule {} without declared configuration", clazz.getName());
177                     } else {
178                         classNames.get(IdMImplementationType.PUSH_CORRELATION_RULE).add(clazz.getName());
179                         pushCRClasses.put(annotation.value(), (Class<? extends PushCorrelationRule>) clazz);
180                     }
181                 } else if (ItemTransformer.class.isAssignableFrom(clazz)
182                         && !clazz.equals(JEXLItemTransformerImpl.class)) {
183 
184                     classNames.get(IdRepoImplementationType.ITEM_TRANSFORMER).add(clazz.getName());
185                 } else if (SchedTaskJobDelegate.class.isAssignableFrom(clazz)
186                         && !PullJobDelegate.class.isAssignableFrom(clazz)
187                         && !PushJobDelegate.class.isAssignableFrom(clazz)
188                         && !GroupMemberProvisionTaskJobDelegate.class.isAssignableFrom(clazz)) {
189 
190                     classNames.get(IdRepoImplementationType.TASKJOB_DELEGATE).add(bd.getBeanClassName());
191                 } else if (ReconFilterBuilder.class.isAssignableFrom(clazz)) {
192                     classNames.get(IdMImplementationType.RECON_FILTER_BUILDER).add(bd.getBeanClassName());
193                 } else if (LogicActions.class.isAssignableFrom(clazz)) {
194                     classNames.get(IdRepoImplementationType.LOGIC_ACTIONS).add(bd.getBeanClassName());
195                 } else if (PropagationActions.class.isAssignableFrom(clazz)) {
196                     classNames.get(IdMImplementationType.PROPAGATION_ACTIONS).add(bd.getBeanClassName());
197                 } else if (PullActions.class.isAssignableFrom(clazz)) {
198                     classNames.get(IdMImplementationType.PULL_ACTIONS).add(bd.getBeanClassName());
199                 } else if (PushActions.class.isAssignableFrom(clazz)) {
200                     classNames.get(IdMImplementationType.PUSH_ACTIONS).add(bd.getBeanClassName());
201                 } else if (PlainAttrValueValidator.class.isAssignableFrom(clazz)) {
202                     classNames.get(IdRepoImplementationType.VALIDATOR).add(bd.getBeanClassName());
203                 } else if (RecipientsProvider.class.isAssignableFrom(clazz)) {
204                     classNames.get(IdRepoImplementationType.RECIPIENTS_PROVIDER).add(bd.getBeanClassName());
205                 } else if (ProvisionSorter.class.isAssignableFrom(clazz)) {
206                     classNames.get(IdMImplementationType.PROVISION_SORTER).add(bd.getBeanClassName());
207                 } else if (Command.class.isAssignableFrom(clazz)) {
208                     classNames.get(IdRepoImplementationType.COMMAND).add(bd.getBeanClassName());
209                 } else {
210                     extImplTypes.forEach((typeName, typeInterface) -> {
211                         Class<?> tic = ClassUtils.resolveClassName(typeInterface, ClassUtils.getDefaultClassLoader());
212                         if (tic.isAssignableFrom(clazz)) {
213                             classNames.get(typeName).add(bd.getBeanClassName());
214                         }
215                     });
216                 }
217             } catch (Throwable t) {
218                 LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
219             }
220         }
221 
222         classNames = Collections.unmodifiableMap(classNames);
223         LOG.debug("Implementation classes found: {}", classNames);
224 
225         reportJobDelegateClasses = Collections.unmodifiableMap(reportJobDelegateClasses);
226         accountRuleClasses = Collections.unmodifiableMap(accountRuleClasses);
227         passwordRuleClasses = Collections.unmodifiableMap(passwordRuleClasses);
228         pullCRClasses = Collections.unmodifiableMap(pullCRClasses);
229         pushCRClasses = Collections.unmodifiableMap(pushCRClasses);
230     }
231 
232     @Override
233     public Set<String> getClassNames(final String type) {
234         return classNames.get(type);
235     }
236 
237     @Override
238     public Class<? extends ReportJobDelegate> getReportClass(final Class<? extends ReportConf> reportConfClass) {
239         return reportJobDelegateClasses.get(reportConfClass);
240     }
241 
242     @Override
243     public Class<? extends AccountRule> getAccountRuleClass(
244             final Class<? extends AccountRuleConf> accountRuleConfClass) {
245 
246         return accountRuleClasses.get(accountRuleConfClass);
247     }
248 
249     @Override
250     public Class<? extends PasswordRule> getPasswordRuleClass(
251             final Class<? extends PasswordRuleConf> passwordRuleConfClass) {
252 
253         return passwordRuleClasses.get(passwordRuleConfClass);
254     }
255 
256     @Override
257     public Class<? extends PullCorrelationRule> getPullCorrelationRuleClass(
258             final Class<? extends PullCorrelationRuleConf> correlationRuleConfClass) {
259 
260         return pullCRClasses.get(correlationRuleConfClass);
261     }
262 
263     @Override
264     public Class<? extends PushCorrelationRule> getPushCorrelationRuleClass(
265             final Class<? extends PushCorrelationRuleConf> correlationRuleConfClass) {
266 
267         return pushCRClasses.get(correlationRuleConfClass);
268     }
269 }