View Javadoc

1   /*
2    * SymmetricDS is an open source database synchronization solution.
3    *   
4    * Copyright (C) Eric Long <erilong@users.sourceforge.net>,
5    *               Andrew Wilcox <andrewbwilcox@users.sourceforge.net>,
6    *               Chris Henson <chenson42@users.sourceforge.net>
7    *
8    * This library is free software; you can redistribute it and/or
9    * modify it under the terms of the GNU Lesser General Public
10   * License as published by the Free Software Foundation; either
11   * version 3 of the License, or (at your option) any later version.
12   *
13   * This library is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this library; if not, see
20   * <http://www.gnu.org/licenses/>.
21   */
22  
23  package org.jumpmind.symmetric.service.impl;
24  
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Types;
28  import java.util.Collections;
29  import java.util.Date;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.commons.math.random.RandomDataImpl;
37  import org.jumpmind.symmetric.common.ParameterConstants;
38  import org.jumpmind.symmetric.model.DataEventAction;
39  import org.jumpmind.symmetric.model.Node;
40  import org.jumpmind.symmetric.model.NodeSecurity;
41  import org.jumpmind.symmetric.service.INodeService;
42  import org.springframework.dao.DataAccessException;
43  import org.springframework.jdbc.core.ResultSetExtractor;
44  import org.springframework.jdbc.core.RowMapper;
45  
46  public class NodeService extends AbstractService implements INodeService {
47  
48      @SuppressWarnings("unused")
49      private static final Log logger = LogFactory.getLog(NodeService.class);
50  
51      private Node nodeIdentity;
52  
53      private Map<String, NodeSecurity> securityCache;
54  
55      private long securityCacheTime;
56  
57      /***
58       * Lookup a node in the database, which contains information for syncing
59       * with it.
60       */
61      @SuppressWarnings("unchecked")
62      public Node findNode(String id) {
63          List<Node> list = jdbcTemplate.query(getSql("findNodeSql"), new Object[] { id }, new NodeRowMapper());
64          return (Node) getFirstEntry(list);
65      }
66  
67      @SuppressWarnings("unchecked")
68      public Node findNodeByExternalId(String nodeGroupId, String externalId) {
69          List<Node> list = jdbcTemplate.query(getSql("findNodeByExternalIdSql"),
70                  new Object[] { nodeGroupId, externalId }, new NodeRowMapper());
71          return (Node) getFirstEntry(list);
72      }
73  
74      public void ignoreNodeChannelForExternalId(boolean enabled, String channelId, String nodeGroupId, String externalId) {
75          Node node = findNodeByExternalId(nodeGroupId, externalId);
76          if (jdbcTemplate.update(getSql("nodeChannelControlIgnoreSql"), new Object[] { enabled ? 1 : 0,
77                  node.getNodeId(), channelId }) == 0) {
78              jdbcTemplate.update(getSql("insertNodeChannelControlSql"), new Object[] { node.getNodeId(), channelId,
79                      enabled ? 1 : 0, 0 });
80          }
81      }
82  
83      public boolean isRegistrationEnabled(String nodeId) {
84          NodeSecurity nodeSecurity = findNodeSecurity(nodeId);
85          if (nodeSecurity != null) {
86              return nodeSecurity.isRegistrationEnabled();
87          }
88          return false;
89      }
90  
91      /***
92       * Lookup a node_security in the database, which contains private
93       * information used to authenticate.
94       */
95      @SuppressWarnings("unchecked")
96      public NodeSecurity findNodeSecurity(String id) {
97          return findNodeSecurity(id, false);
98      }
99  
100     @SuppressWarnings("unchecked")
101     public NodeSecurity findNodeSecurity(String id, boolean createIfNotFound) {
102         List<NodeSecurity> list = jdbcTemplate.query(getSql("findNodeSecuritySql"), new Object[] { id },
103                 new NodeSecurityRowMapper());
104         NodeSecurity security = (NodeSecurity) getFirstEntry(list);
105         if (security == null && createIfNotFound) {
106             insertNodeSecurity(id);
107             security = findNodeSecurity(id, false);
108         }
109         return security;
110     }
111 
112     public void insertNodeSecurity(String id) {
113         flushNodeAuthorizedCache();
114         jdbcTemplate.update(getSql("insertNodeSecuritySql"), new Object[] { id, generatePassword(), findIdentity().getNodeId() });
115     }
116 
117     public boolean updateNode(Node node) {
118         boolean updated = jdbcTemplate.update(getSql("updateNodeSql"), new Object[] { node.getNodeGroupId(),
119                 node.getExternalId(), node.getDatabaseType(), node.getDatabaseVersion(), node.getSchemaVersion(),
120                 node.getSymmetricVersion(), node.getSyncURL(), node.getHeartbeatTime(), node.isSyncEnabled() ? 1 : 0,
121                 node.getTimezoneOffset(), node.getCreatedByNodeId(), node.getNodeId() }) == 1;
122         return updated;
123     }
124 
125     protected <T> T getFirstEntry(List<T> list) {
126         if (list != null && list.size() > 0) {
127             return list.get(0);
128         }
129         return null;
130     }
131 
132     /***
133      * Check that the given node and password match in the node_security table.
134      * A node must authenticate before it's allowed to sync data.
135      */
136     @SuppressWarnings("unchecked")
137     public boolean isNodeAuthorized(String id, String password) {
138         long maxSecurityCacheTime = parameterService
139                 .getLong(ParameterConstants.NODE_SECURITY_CACHE_REFRESH_PERIOD_IN_MS);
140         if (System.currentTimeMillis() - securityCacheTime >= maxSecurityCacheTime || securityCacheTime == 0) {
141             securityCache = (Map<String, NodeSecurity>) jdbcTemplate.query(getSql("findAllNodeSecuritySql"),
142                     new NodeSecurityResultSetExtractor());
143             securityCacheTime = System.currentTimeMillis();
144         }
145 
146         NodeSecurity nodeSecurity = securityCache.get(id);
147         if (nodeSecurity != null
148                 && ((nodeSecurity.getPassword() != null && !nodeSecurity.getPassword().equals("") && nodeSecurity
149                         .getPassword().equals(password)) || nodeSecurity.isRegistrationEnabled())) {
150             return true;
151         }
152         return false;
153     }
154 
155     public void flushNodeAuthorizedCache() {
156         securityCacheTime = 0;
157     }
158 
159     public Node findIdentity() {
160         return findIdentity(true);
161     }
162 
163     @SuppressWarnings("unchecked")
164     public Node findIdentity(boolean useCache) {
165         if (nodeIdentity == null || useCache == false) {
166             List<Node> list = jdbcTemplate.query(getSql("findNodeIdentitySql"), new NodeRowMapper());
167             nodeIdentity = (Node) getFirstEntry(list);
168         }
169         return nodeIdentity;
170     }
171 
172     public List<Node> findNodesToPull() {
173         return findSourceNodesFor(DataEventAction.WAIT_FOR_POLL);
174     }
175 
176     public List<Node> findNodesToPushTo() {
177         return findTargetNodesFor(DataEventAction.PUSH);
178     }
179 
180     @SuppressWarnings("unchecked")
181     public List<Node> findSourceNodesFor(DataEventAction eventAction) {
182         Node node = findIdentity();
183         if (node != null) {
184             return jdbcTemplate.query(getSql("findNodesWhoTargetMeSql"), new Object[] { node.getNodeGroupId(),
185                     eventAction.getCode() }, new NodeRowMapper());
186         } else {
187             return Collections.emptyList();
188         }
189     }
190 
191     @SuppressWarnings("unchecked")
192     public List<Node> findTargetNodesFor(DataEventAction eventAction) {
193         Node node = findIdentity();
194         if (node != null) {
195             return jdbcTemplate.query(getSql("findNodesWhoITargetSql"), new Object[] { node.getNodeGroupId(),
196                     eventAction.getCode() }, new NodeRowMapper());
197         } else {
198             return Collections.emptyList();
199         }
200     }
201 
202     public boolean updateNodeSecurity(NodeSecurity security) {
203         flushNodeAuthorizedCache();
204         return jdbcTemplate.update(getSql("updateNodeSecuritySql"), new Object[] { security.getPassword(),
205                 security.isRegistrationEnabled() ? 1 : 0, security.getRegistrationTime(),
206                 security.isInitialLoadEnabled() ? 1 : 0, security.getInitialLoadTime(),
207                 security.getCreatedByNodeId(), security.getNodeId() }, new int[] { Types.VARCHAR, Types.INTEGER,
208                 Types.TIMESTAMP, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR }) == 1;
209     }
210 
211     public boolean setInitialLoadEnabled(String nodeId, boolean initialLoadEnabled) {
212         NodeSecurity nodeSecurity = findNodeSecurity(nodeId, true);
213         if (nodeSecurity != null) {
214             nodeSecurity.setInitialLoadEnabled(initialLoadEnabled);
215             if (initialLoadEnabled) {
216                 nodeSecurity.setInitialLoadTime(null);
217             } else {
218                 nodeSecurity.setInitialLoadTime(new Date());
219             }
220             return updateNodeSecurity(nodeSecurity);
221         }
222         return false;
223     }
224 
225     /***
226      * Generate a secure random password for a node.
227      */
228     // TODO: nodeGenerator.generatePassword();
229     public String generatePassword() {
230         return new RandomDataImpl().nextSecureHexString(30);
231     }
232 
233     /***
234      * Generate the next node ID that is available. Try to use the domain ID as
235      * the node ID.
236      */
237     // TODO: nodeGenerator.generateNodeId();
238     public String generateNodeId(String nodeGroupId, String externalId) {
239         String nodeId = externalId;
240         int maxTries = 100;
241         for (int sequence = 0; sequence < maxTries; sequence++) {
242             if (findNode(nodeId) == null) {
243                 return nodeId;
244             }
245             nodeId = externalId + "-" + sequence;
246         }
247         throw new RuntimeException("Could not find nodeId for externalId of " + externalId + " after " + maxTries
248                 + " tries.");
249     }
250 
251     class NodeRowMapper implements RowMapper {
252         public Object mapRow(ResultSet rs, int num) throws SQLException {
253             Node node = new Node();
254             node.setNodeId(rs.getString(1));
255             node.setNodeGroupId(rs.getString(2));
256             node.setExternalId(rs.getString(3));
257             node.setSyncEnabled(rs.getBoolean(4));
258             node.setSyncURL(rs.getString(5));
259             node.setSchemaVersion(rs.getString(6));
260             node.setDatabaseType(rs.getString(7));
261             node.setDatabaseVersion(rs.getString(8));
262             node.setSymmetricVersion(rs.getString(9));
263             node.setCreatedByNodeId(rs.getString(10));
264             return node;
265         }
266     }
267 
268     class NodeSecurityRowMapper implements RowMapper {
269         public Object mapRow(ResultSet rs, int num) throws SQLException {
270             NodeSecurity nodeSecurity = new NodeSecurity();
271             nodeSecurity.setNodeId(rs.getString(1));
272             nodeSecurity.setPassword(rs.getString(2));
273             nodeSecurity.setRegistrationEnabled(rs.getBoolean(3));
274             nodeSecurity.setRegistrationTime(rs.getTimestamp(4));
275             nodeSecurity.setInitialLoadEnabled(rs.getBoolean(5));
276             nodeSecurity.setInitialLoadTime(rs.getTimestamp(6));
277             nodeSecurity.setCreatedByNodeId(rs.getString(7));
278             return nodeSecurity;
279         }
280     }
281 
282     class NodeSecurityResultSetExtractor implements ResultSetExtractor {
283         public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
284             Map<String, NodeSecurity> result = new HashMap<String, NodeSecurity>();
285             NodeSecurityRowMapper mapper = new NodeSecurityRowMapper();
286 
287             while (rs.next()) {
288                 NodeSecurity nodeSecurity = (NodeSecurity) mapper.mapRow(rs, 0);
289                 result.put(nodeSecurity.getNodeId(), nodeSecurity);
290             }
291             return result;
292         }
293     }
294 
295     public boolean isExternalIdRegistered(String nodeGroupId, String externalId) {
296         return jdbcTemplate.queryForInt(getSql("isNodeRegisteredSql"), new Object[] { nodeGroupId, externalId }) > 0;
297     }
298 
299 }