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.common.keymaster.client.zookeeper;
20  
21  import com.fasterxml.jackson.databind.json.JsonMapper;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Objects;
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.curator.framework.CuratorFramework;
28  import org.apache.curator.framework.recipes.cache.CuratorCache;
29  import org.apache.syncope.common.keymaster.client.api.DomainOps;
30  import org.apache.syncope.common.keymaster.client.api.DomainWatcher;
31  import org.apache.syncope.common.keymaster.client.api.KeymasterException;
32  import org.apache.syncope.common.keymaster.client.api.model.Domain;
33  import org.apache.syncope.common.lib.SyncopeConstants;
34  import org.apache.syncope.common.lib.types.CipherAlgorithm;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  import org.springframework.beans.factory.InitializingBean;
38  import org.springframework.beans.factory.annotation.Autowired;
39  
40  /**
41   * Implements {@link DomainOps} via Apache Curator / Zookeeper.
42   */
43  public class ZookeeperDomainOps implements DomainOps, InitializingBean {
44  
45      protected static final Logger LOG = LoggerFactory.getLogger(DomainOps.class);
46  
47      protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
48  
49      protected static final String DOMAIN_PATH = "/domains";
50  
51      protected static String buildDomainPath(final String... parts) {
52          String prefix = DOMAIN_PATH;
53          String suffix = StringUtils.EMPTY;
54          if (parts != null && parts.length > 0) {
55              suffix = '/' + String.join("/", parts);
56          }
57          return prefix + suffix;
58      }
59  
60      @Autowired
61      protected CuratorFramework client;
62  
63      @Autowired(required = false)
64      protected DomainWatcher watcher;
65  
66      @Override
67      public void afterPropertiesSet() throws Exception {
68          if (watcher != null) {
69              if (client.checkExists().forPath(buildDomainPath()) == null) {
70                  client.create().creatingParentContainersIfNeeded().forPath(buildDomainPath());
71              }
72  
73              CuratorCache cache = CuratorCache.build(client, buildDomainPath());
74              cache.listenable().addListener((type, oldData, newData) -> {
75                  switch (type) {
76                      case NODE_CREATED:
77                          LOG.debug("Domain {} added", newData.getPath());
78                          try {
79                              Domain domain = MAPPER.readValue(newData.getData(), Domain.class);
80  
81                              LOG.info("Domain {} created", domain.getKey());
82                              watcher.added(domain);
83                          } catch (IOException e) {
84                              LOG.debug("Could not parse {}", new String(newData.getData()), e);
85                          }
86                          break;
87  
88                      case NODE_CHANGED:
89                          LOG.debug("Domain {} updated", newData.getPath());
90                          break;
91  
92                      case NODE_DELETED:
93                          LOG.debug("Domain {} removed", newData.getPath());
94                          watcher.removed(StringUtils.substringAfter(newData.getPath(), DOMAIN_PATH + '/'));
95                          break;
96  
97                      default:
98                          LOG.debug("Event {} received with data {}", type, newData);
99                  }
100             });
101             cache.start();
102         }
103     }
104 
105     @Override
106     public List<Domain> list() {
107         try {
108             if (client.checkExists().forPath(buildDomainPath()) == null) {
109                 client.create().creatingParentContainersIfNeeded().forPath(buildDomainPath());
110             }
111 
112             List<Domain> list = new ArrayList<>();
113             for (String child : client.getChildren().forPath(buildDomainPath())) {
114                 list.add(MAPPER.readValue(client.getData().forPath(buildDomainPath(child)), Domain.class));
115             }
116 
117             return list;
118         } catch (Exception e) {
119             throw new KeymasterException(e);
120         }
121     }
122 
123     @Override
124     public Domain read(final String key) {
125         try {
126             return MAPPER.readValue(client.getData().forPath(buildDomainPath(key)), Domain.class);
127         } catch (Exception e) {
128             throw new KeymasterException(e);
129         }
130     }
131 
132     @Override
133     public void create(final Domain domain) {
134         if (Objects.equals(domain.getKey(), SyncopeConstants.MASTER_DOMAIN)) {
135             throw new KeymasterException("Cannot create domain " + SyncopeConstants.MASTER_DOMAIN);
136         }
137 
138         try {
139             if (client.checkExists().forPath(buildDomainPath(domain.getKey())) != null) {
140                 throw new KeymasterException("Domain " + domain.getKey() + " existing");
141             }
142 
143             client.create().creatingParentContainersIfNeeded().
144                     forPath(buildDomainPath(domain.getKey()), MAPPER.writeValueAsBytes(domain));
145         } catch (KeymasterException e) {
146             throw e;
147         } catch (Exception e) {
148             throw new KeymasterException(e);
149         }
150     }
151 
152     @Override
153     public void changeAdminPassword(
154             final String key, final String password, final CipherAlgorithm cipherAlgorithm) {
155 
156         try {
157             Domain domain = read(key);
158 
159             domain.setAdminPassword(password);
160             domain.setAdminCipherAlgorithm(cipherAlgorithm);
161             client.setData().forPath(buildDomainPath(key), MAPPER.writeValueAsBytes(domain));
162         } catch (KeymasterException e) {
163             throw e;
164         } catch (Exception e) {
165             throw new KeymasterException(e);
166         }
167     }
168 
169     @Override
170     public void adjustPoolSize(final String key, final int poolMaxActive, final int poolMinIdle) {
171         try {
172             Domain domain = read(key);
173 
174             domain.setPoolMaxActive(poolMaxActive);
175             domain.setPoolMinIdle(poolMinIdle);
176             client.setData().forPath(buildDomainPath(key), MAPPER.writeValueAsBytes(domain));
177         } catch (KeymasterException e) {
178             throw e;
179         } catch (Exception e) {
180             throw new KeymasterException(e);
181         }
182     }
183 
184     @Override
185     public void delete(final String key) {
186         try {
187             client.delete().forPath(buildDomainPath(key));
188         } catch (Exception e) {
189             throw new KeymasterException(e);
190         }
191     }
192 }