1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
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 }