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.spring.implementation;
20  
21  import groovy.lang.GroovyClassLoader;
22  import java.lang.reflect.Modifier;
23  import java.lang.reflect.ParameterizedType;
24  import java.lang.reflect.Type;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.function.Consumer;
30  import java.util.function.Supplier;
31  import org.apache.commons.lang3.tuple.Pair;
32  import org.apache.syncope.common.lib.command.CommandArgs;
33  import org.apache.syncope.common.lib.policy.AccountRuleConf;
34  import org.apache.syncope.common.lib.policy.PasswordRuleConf;
35  import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
36  import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
37  import org.apache.syncope.common.lib.report.ReportConf;
38  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
39  import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
40  import org.apache.syncope.core.persistence.api.entity.Implementation;
41  import org.apache.syncope.core.provisioning.api.ImplementationLookup;
42  import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
43  import org.apache.syncope.core.provisioning.api.rules.AccountRule;
44  import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
45  import org.apache.syncope.core.provisioning.api.rules.PullCorrelationRule;
46  import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
47  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
48  import org.apache.syncope.core.spring.ApplicationContextProvider;
49  import org.springframework.beans.factory.support.AbstractBeanDefinition;
50  
51  public final class ImplementationManager {
52  
53      private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
54  
55      private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
56  
57      @SuppressWarnings("unchecked")
58      public static Optional<ReportJobDelegate> buildReportJobDelegate(
59              final Implementation impl,
60              final Supplier<ReportJobDelegate> cacheGetter,
61              final Consumer<ReportJobDelegate> cachePutter)
62              throws ClassNotFoundException {
63  
64          switch (impl.getEngine()) {
65              case GROOVY:
66                  return Optional.of(build(impl, cacheGetter, cachePutter));
67  
68              case JAVA:
69              default:
70                  ReportConf conf = POJOHelper.deserialize(impl.getBody(), ReportConf.class);
71                  Class<ReportJobDelegate> clazz =
72                          (Class<ReportJobDelegate>) ApplicationContextProvider.getApplicationContext().
73                                  getBean(ImplementationLookup.class).getReportClass(conf.getClass());
74  
75                  if (clazz == null) {
76                      return Optional.empty();
77                  }
78  
79                  ReportJobDelegate report = build(clazz, true, cacheGetter, cachePutter);
80                  report.setConf(conf);
81                  return Optional.of(report);
82          }
83      }
84  
85      @SuppressWarnings("unchecked")
86      public static Optional<AccountRule> buildAccountRule(
87              final Implementation impl,
88              final Supplier<AccountRule> cacheGetter,
89              final Consumer<AccountRule> cachePutter)
90              throws ClassNotFoundException {
91  
92          switch (impl.getEngine()) {
93              case GROOVY:
94                  return Optional.of(build(impl, cacheGetter, cachePutter));
95  
96              case JAVA:
97              default:
98                  AccountRuleConf conf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
99                  Class<AccountRule> clazz = (Class<AccountRule>) ApplicationContextProvider.getApplicationContext().
100                         getBean(ImplementationLookup.class).getAccountRuleClass(conf.getClass());
101 
102                 if (clazz == null) {
103                     return Optional.empty();
104                 }
105 
106                 AccountRule rule = build(clazz, true, cacheGetter, cachePutter);
107                 rule.setConf(conf);
108                 return Optional.of(rule);
109         }
110     }
111 
112     @SuppressWarnings("unchecked")
113     public static Optional<PasswordRule> buildPasswordRule(
114             final Implementation impl,
115             final Supplier<PasswordRule> cacheGetter,
116             final Consumer<PasswordRule> cachePutter)
117             throws ClassNotFoundException {
118 
119         switch (impl.getEngine()) {
120             case GROOVY:
121                 return Optional.of(build(impl, cacheGetter, cachePutter));
122 
123             case JAVA:
124             default:
125                 PasswordRuleConf conf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
126                 Class<PasswordRule> clazz = (Class<PasswordRule>) ApplicationContextProvider.getApplicationContext().
127                         getBean(ImplementationLookup.class).getPasswordRuleClass(conf.getClass());
128 
129                 if (clazz == null) {
130                     return Optional.empty();
131                 }
132 
133                 PasswordRule rule = build(clazz, true, cacheGetter, cachePutter);
134                 rule.setConf(conf);
135                 return Optional.of(rule);
136         }
137     }
138 
139     @SuppressWarnings("unchecked")
140     public static Optional<PullCorrelationRule> buildPullCorrelationRule(
141             final Implementation impl,
142             final Supplier<PullCorrelationRule> cacheGetter,
143             final Consumer<PullCorrelationRule> cachePutter)
144             throws ClassNotFoundException {
145 
146         switch (impl.getEngine()) {
147             case GROOVY:
148                 return Optional.of(build(impl, cacheGetter, cachePutter));
149 
150             case JAVA:
151             default:
152                 PullCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
153                 Class<PullCorrelationRule> clazz =
154                         (Class<PullCorrelationRule>) ApplicationContextProvider.getApplicationContext().
155                                 getBean(ImplementationLookup.class).getPullCorrelationRuleClass(conf.getClass());
156 
157                 if (clazz == null) {
158                     return Optional.empty();
159                 }
160 
161                 PullCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
162                 rule.setConf(conf);
163                 return Optional.of(rule);
164         }
165     }
166 
167     @SuppressWarnings("unchecked")
168     public static Optional<PushCorrelationRule> buildPushCorrelationRule(
169             final Implementation impl,
170             final Supplier<PushCorrelationRule> cacheGetter,
171             final Consumer<PushCorrelationRule> cachePutter)
172             throws ClassNotFoundException {
173 
174         switch (impl.getEngine()) {
175             case GROOVY:
176                 return Optional.of(build(impl, cacheGetter, cachePutter));
177 
178             case JAVA:
179             default:
180                 PushCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
181                 Class<PushCorrelationRule> clazz =
182                         (Class<PushCorrelationRule>) ApplicationContextProvider.getApplicationContext().
183                                 getBean(ImplementationLookup.class).getPushCorrelationRuleClass(conf.getClass());
184 
185                 if (clazz == null) {
186                     return Optional.empty();
187                 }
188 
189                 PushCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
190                 rule.setConf(conf);
191                 return Optional.of(rule);
192         }
193     }
194 
195     @SuppressWarnings("unchecked")
196     private static Class<? extends CommandArgs> findCommandArgsClass(final Type type) {
197         if (type.getTypeName().startsWith(
198                 ImplementationTypesHolder.getInstance().getValues().get(IdRepoImplementationType.COMMAND) + "<")) {
199 
200             return (Class<? extends CommandArgs>) ((ParameterizedType) type).getActualTypeArguments()[0];
201         }
202 
203         if (type instanceof Class) {
204             for (Type i : ((Class) type).getGenericInterfaces()) {
205                 Class<? extends CommandArgs> r = findCommandArgsClass(i);
206                 if (r != null) {
207                     return r;
208                 }
209             }
210         }
211 
212         return null;
213     }
214 
215     public static CommandArgs emptyArgs(final Implementation impl) throws Exception {
216         if (!IdRepoImplementationType.COMMAND.equals(impl.getType())) {
217             throw new IllegalArgumentException("This method can be only called on implementations");
218         }
219 
220         Class<Object> commandClass = getClass(impl).getLeft();
221 
222         Class<? extends CommandArgs> commandArgsClass = findCommandArgsClass(commandClass);
223         if (commandArgsClass != null
224                 && (commandArgsClass.getEnclosingClass() == null
225                 || Modifier.isStatic(commandArgsClass.getModifiers()))) {
226 
227             return commandArgsClass.getDeclaredConstructor().newInstance();
228         }
229 
230         throw new IllegalArgumentException(
231                 CommandArgs.class.getName() + " shall be either declared as independent or nested static");
232     }
233 
234     @SuppressWarnings("unchecked")
235     private static <T> Pair<Class<T>, Boolean> getClass(final Implementation impl) throws ClassNotFoundException {
236         if (CLASS_CACHE.containsKey(impl.getKey())) {
237             return Pair.of((Class<T>) CLASS_CACHE.get(impl.getKey()), true);
238         }
239 
240         Class<?> clazz;
241         switch (impl.getEngine()) {
242             case GROOVY:
243                 clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
244                 break;
245 
246             case JAVA:
247             default:
248                 clazz = Class.forName(impl.getBody());
249         }
250 
251         CLASS_CACHE.put(impl.getKey(), clazz);
252         return Pair.of((Class<T>) clazz, false);
253     }
254 
255     @SuppressWarnings("unchecked")
256     public static <T> T build(final Implementation impl) throws ClassNotFoundException {
257         return (T) ApplicationContextProvider.getBeanFactory().
258                 createBean(getClass(impl).getLeft(), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
259     }
260 
261     @SuppressWarnings("unchecked")
262     private static <T> T build(
263             final Class<T> clazz,
264             final boolean classCached,
265             final Supplier<T> cacheGetter,
266             final Consumer<T> cachePutter) {
267 
268         boolean perContext = Optional.ofNullable(clazz.getAnnotation(SyncopeImplementation.class)).
269                 map(ann -> ann.scope() == InstanceScope.PER_CONTEXT).
270                 orElse(true);
271         T instance = null;
272         if (perContext && classCached) {
273             instance = cacheGetter.get();
274         }
275         if (instance == null) {
276             instance = (T) ApplicationContextProvider.getBeanFactory().
277                     createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
278 
279             if (perContext) {
280                 cachePutter.accept(instance);
281             }
282         }
283 
284         return instance;
285     }
286 
287     public static <T> T build(final Implementation impl, final Supplier<T> cacheGetter, final Consumer<T> cachePutter)
288             throws ClassNotFoundException {
289 
290         Pair<Class<T>, Boolean> clazz = getClass(impl);
291 
292         return build(clazz.getLeft(), clazz.getRight(), cacheGetter, cachePutter);
293     }
294 
295     public static Class<?> purge(final String implementation) {
296         return CLASS_CACHE.remove(implementation);
297     }
298 
299     private ImplementationManager() {
300         // private constructor for static utility class
301     }
302 }