Mercurial Hosting > nabble
annotate src/nabble/model/SiteImpl.java @ 2:abe0694e9849
replace local_dir with home_dir
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Mon, 25 Mar 2019 13:59:13 -0600 |
parents | 7ecd1a4ef557 |
children | 1010f557927f |
rev | line source |
---|---|
0 | 1 package nabble.model; |
2 | |
3 import fschmidt.db.DbDatabase; | |
4 import fschmidt.db.DbObjectFactory; | |
5 import fschmidt.db.DbRecord; | |
6 import fschmidt.db.DbTable; | |
7 import fschmidt.db.DbUtils; | |
8 import fschmidt.db.Listener; | |
9 import fschmidt.db.ListenerList; | |
10 import fschmidt.db.LongKey; | |
11 import fschmidt.db.NoKey; | |
12 import fschmidt.db.NoKeySetter; | |
13 import fschmidt.util.java.CollectionUtils; | |
14 import fschmidt.util.java.Computable; | |
15 import fschmidt.util.java.FutureValue; | |
16 import fschmidt.util.java.Memoizer; | |
17 import fschmidt.util.java.SimpleCache; | |
18 import jdbcpgbackup.DataFilter; | |
19 import jdbcpgbackup.ZipBackup; | |
20 import nabble.model.export.NodeData; | |
21 import nabble.modules.ModuleManager; | |
22 import nabble.naml.compiler.CompileException; | |
23 import nabble.naml.compiler.Module; | |
24 import nabble.naml.compiler.Program; | |
25 import nabble.naml.compiler.StackTraceElement; | |
26 import nabble.naml.compiler.Template; | |
27 import nabble.naml.compiler.TemplatePrintWriter; | |
28 import nabble.naml.namespaces.BasicNamespace; | |
29 import nabble.view.web.template.NabbleNamespace; | |
30 import org.slf4j.Logger; | |
31 import org.slf4j.LoggerFactory; | |
32 | |
33 import java.io.File; | |
34 import java.io.StringWriter; | |
35 import java.sql.Connection; | |
36 import java.sql.PreparedStatement; | |
37 import java.sql.ResultSet; | |
38 import java.sql.SQLException; | |
39 import java.sql.Statement; | |
40 import java.util.ArrayList; | |
41 import java.util.Calendar; | |
42 import java.util.Collection; | |
43 import java.util.Collections; | |
44 import java.util.Date; | |
45 import java.util.HashMap; | |
46 import java.util.HashSet; | |
47 import java.util.Iterator; | |
48 import java.util.List; | |
49 import java.util.Map; | |
50 import java.util.Set; | |
51 import java.util.WeakHashMap; | |
52 import java.util.concurrent.CopyOnWriteArrayList; | |
53 | |
54 | |
55 final class SiteImpl implements Site { | |
56 private static final Logger logger = LoggerFactory.getLogger(SiteImpl.class); | |
57 | |
58 final SiteKey siteKey; | |
59 private final DbRecord<NoKey,SiteImpl> record; | |
60 private long rootNodeId; | |
61 private NodeImpl rootNode = null; | |
62 | |
63 private final Object tweakLock = new Object(); | |
64 private CompileException tweakException = null; | |
65 private Program program = null; | |
66 private final Date whenCreated; | |
67 | |
68 SiteImpl(SiteKey siteKey) { | |
69 this.siteKey = siteKey; | |
70 record = table(siteKey).newRecord(this); | |
71 record.fields().put( "root_node_id", 0L ); | |
72 whenCreated = new Date(); | |
73 } | |
74 | |
75 void setRoot(NodeImpl node) { | |
76 if( !node.isInDb() ) | |
77 throw new RuntimeException("node must be in db"); | |
78 this.rootNodeId = node.getId(); | |
79 record.fields().put( "root_node_id", rootNodeId ); | |
80 this.rootNode = node; | |
81 record.update(); | |
82 } | |
83 | |
84 private SiteImpl(SiteKey siteKey,NoKey key,ResultSet rs) | |
85 throws SQLException | |
86 { | |
87 this.siteKey = siteKey; | |
88 record = table(siteKey).newRecord(this,key); | |
89 rootNodeId = rs.getLong("root_node_id"); | |
90 whenCreated = rs.getTimestamp("when_created"); | |
91 for( ExtensionFactory<Site,?> factory : extensionFactories ) { | |
92 Object obj = factory.construct(this,rs); | |
93 if( obj != null ) | |
94 getExtensionMap().put(factory,obj); | |
95 } | |
96 } | |
97 | |
98 public DbRecord<NoKey,SiteImpl> getDbRecord() { | |
99 return record; | |
100 } | |
101 | |
102 private DbTable<NoKey,SiteImpl> table() { | |
103 return record.getDbTable(); | |
104 } | |
105 | |
106 private DbDatabase db() { | |
107 return table().getDbDatabase(); | |
108 } | |
109 | |
110 public DbDatabase getDb() { | |
111 return siteKey.getDb(); | |
112 } | |
113 | |
114 public long getId() { | |
115 return siteKey.getId(); | |
116 } | |
117 /* | |
118 private void calcBaseUrl() { | |
119 siteGlobal().calcBaseUrl(); | |
120 } | |
121 */ | |
122 long getRootNodeId() { | |
123 return rootNodeId; | |
124 } | |
125 | |
126 NodeImpl getRootNodeImpl() { | |
127 if( DbUtils.isStale(rootNode) ) { | |
128 rootNode = NodeImpl.getNode(siteKey,rootNodeId); | |
129 } | |
130 return rootNode; | |
131 } | |
132 | |
133 public Node getRootNode() { | |
134 return getRootNodeImpl(); | |
135 } | |
136 | |
137 public Date getWhenCreated() { | |
138 return whenCreated; | |
139 } | |
140 | |
141 /** To be called from the shell */ | |
142 public void setWhenCreated(int day, int month, int year) { | |
143 Calendar cal = Calendar.getInstance(); | |
144 cal.set(Calendar.DAY_OF_MONTH, day); | |
145 cal.set(Calendar.MONTH, month); | |
146 cal.set(Calendar.YEAR, year); | |
147 DbRecord<NoKey,?> record = getDbRecord(); | |
148 record.fields().put("when_created", cal.getTime()); | |
149 record.update(); | |
150 } | |
151 | |
152 SiteGlobal siteGlobal() { | |
153 return siteKey.siteGlobal(); | |
154 } | |
155 | |
156 @Override public boolean equals(Object obj) { | |
157 return this==obj || obj instanceof SiteImpl && record.isInDb() && ((SiteImpl)obj).getId()==getId(); | |
158 } | |
159 | |
160 @Override public int hashCode() { | |
161 return (int)getId(); | |
162 } | |
163 | |
164 public Program getProgram() { | |
165 synchronized(tweakLock) { | |
166 if( program == null ) { | |
167 if(trace()) logger.error("getting Program for "+this+" "+System.identityHashCode(this)+" tweakException="+tweakException); | |
168 List<Module> modules = ModuleManager.getModules(SiteImpl.this); | |
169 program = Program.getInstance(modules); | |
170 } | |
171 return program; | |
172 } | |
173 } | |
174 | |
175 public Template getTemplate(String templateName,Class... base) { | |
176 synchronized(tweakLock) { | |
177 try { | |
178 return getProgram().getTemplate(templateName,base); | |
179 } catch(CompileException e) { | |
180 if( setTweakException(e) ) { | |
181 return getTemplate(templateName,base); | |
182 } | |
183 throw new RuntimeException(""+this+" "+System.identityHashCode(this),e); | |
184 } | |
185 } | |
186 } | |
187 | |
188 public void setCustomDomain(String customDomain) { | |
189 siteGlobal().setCustomDomain(customDomain); | |
190 } | |
191 | |
192 public String getCustomDomain() { | |
193 return siteGlobal().getCustomDomain(); | |
194 } | |
195 | |
196 | |
197 public String getBaseUrl() { | |
198 return siteGlobal().getBaseUrl(); | |
199 } | |
200 | |
201 List<UserImpl> getPosters() { | |
202 try { | |
203 Connection con = db().getConnection(); | |
204 PreparedStatement stmt = con.prepareStatement( | |
205 "select * from user_ where user_id in (" | |
206 +"select distinct owner_id from node where redirect is null" | |
207 +")" | |
208 ); | |
209 try { | |
210 return UserImpl.getUsers(siteKey,stmt); | |
211 } finally { | |
212 stmt.close(); | |
213 con.close(); | |
214 } | |
215 } catch(SQLException e) { | |
216 throw new RuntimeException(e); | |
217 } | |
218 } | |
219 | |
220 public List<User> getUsers(String cnd) { | |
221 List<User> list = new ArrayList<User>(); | |
222 getUserImpls(list, cnd); | |
223 return list; | |
224 } | |
225 | |
226 List<UserImpl> getUserImpls(String cnd) { | |
227 List<UserImpl> list = new ArrayList<UserImpl>(); | |
228 getUserImpls(list, cnd); | |
229 return list; | |
230 } | |
231 | |
232 void getUserImpls(List<? super UserImpl> list, String cnd) { | |
233 try { | |
234 Connection con = db().getConnection(); | |
235 PreparedStatement stmt = con.prepareStatement( | |
236 "select * from user_" + | |
237 (cnd == null? "" : " where " + cnd) | |
238 ); | |
239 try { | |
240 UserImpl.getUsers(siteKey,stmt,list); | |
241 } finally { | |
242 stmt.close(); | |
243 con.close(); | |
244 } | |
245 } catch(SQLException e) { | |
246 throw new RuntimeException(e); | |
247 } | |
248 } | |
249 | |
250 public int getActivity() { | |
251 return siteGlobal().getActivity(); | |
252 } | |
253 | |
254 public void setActivity(int activity) { | |
255 siteGlobal().setActivity(activity); | |
256 } | |
257 | |
258 public boolean isEmbarrassing() { | |
259 return siteGlobal().isEmbarrassing(); | |
260 } | |
261 | |
262 public void setEmbarrassing(boolean isEmbarrassing) { | |
263 siteGlobal().setEmbarrassing(isEmbarrassing); | |
264 } | |
265 | |
266 | |
267 public String toString() { | |
268 return "site-"+getId(); | |
269 } | |
270 | |
271 static final ListenerList<SiteImpl> postUpdateListeners = new ListenerList<SiteImpl>(); | |
272 private static final ListenerList<SiteImpl> postDeleteListeners = new ListenerList<SiteImpl>(); | |
273 private static final ListenerList<SiteImpl> preInsertListeners = new ListenerList<SiteImpl>(); | |
274 private static final ListenerList<SiteImpl> preUpdateListeners = new ListenerList<SiteImpl>(); | |
275 | |
276 private static Computable<SiteKey,DbTable<NoKey,SiteImpl>> tables = new SimpleCache<SiteKey,DbTable<NoKey,SiteImpl>>(new WeakHashMap<SiteKey,DbTable<NoKey,SiteImpl>>(), new Computable<SiteKey,DbTable<NoKey,SiteImpl>>() { | |
277 public DbTable<NoKey,SiteImpl> get(SiteKey siteKey) { | |
278 DbDatabase db = siteKey.getDb(); | |
279 final long siteId = siteKey.getId(); | |
280 DbTable<NoKey,SiteImpl> table = db.newTable("site",NoKeySetter.INSTANCE | |
281 , new DbObjectFactory<NoKey,SiteImpl>() { | |
282 public SiteImpl makeDbObject(NoKey key,ResultSet rs,String tableName) | |
283 throws SQLException | |
284 { | |
285 SiteKey siteKey = SiteKey.getInstance(siteId); | |
286 return new SiteImpl(siteKey,key,rs); | |
287 } | |
288 } | |
289 ); | |
290 table.getPreInsertListeners().add(preInsertListeners); | |
291 table.getPreUpdateListeners().add(preUpdateListeners); | |
292 table.getPostUpdateListeners().add(postUpdateListeners); | |
293 table.getPostDeleteListeners().add(postDeleteListeners); | |
294 return table; | |
295 } | |
296 }); | |
297 | |
298 private static DbTable<NoKey,SiteImpl> table(SiteKey siteKey) { | |
299 return tables.get(siteKey); | |
300 } | |
301 | |
302 static SiteImpl getSite(SiteKey siteKey,long siteId) { | |
303 return table(siteKey).findByPrimaryKey(NoKey.INSTANCE); | |
304 } | |
305 | |
306 static SiteImpl getSite(SiteKey siteKey,ResultSet rs) | |
307 throws SQLException | |
308 { | |
309 return table(siteKey).getDbObject(rs); | |
310 } | |
311 | |
312 public Date getDeleteDate() { | |
313 return siteGlobal().getDeleteDate(); | |
314 } | |
315 | |
316 public void clearDeleteDate() { | |
317 siteGlobal().clearDeleteDate(); | |
318 } | |
319 | |
320 | |
321 | |
322 | |
323 | |
324 | |
325 | |
326 static void addChangeListener(final Listener<? super SiteImpl> listener) { | |
327 postUpdateListeners.add(listener); | |
328 postDeleteListeners.add(listener); | |
329 } | |
330 | |
331 static void addPreChangeListener(final Listener<? super SiteImpl> listener) { | |
332 preInsertListeners.add(listener); | |
333 preUpdateListeners.add(listener); | |
334 } | |
335 | |
336 | |
337 public User getUser(long id) { | |
338 return getUserImpl(id); | |
339 } | |
340 | |
341 UserImpl getUserImpl(long id) { | |
342 return UserImpl.getUser(siteKey,id); | |
343 } | |
344 | |
345 public User getUserFromEmail(String email) { | |
346 return getUserImplFromEmail(email); | |
347 } | |
348 | |
349 UserImpl getUserImplFromEmail(String email) { | |
350 return UserImpl.getUserFromEmail(this,email); | |
351 } | |
352 | |
353 public User getUserFromName(String name) { | |
354 return getUserImplFromName(name); | |
355 } | |
356 | |
357 UserImpl getUserImplFromName(String name) { | |
358 return UserImpl.getUserFromName(this,name); | |
359 } | |
360 | |
361 public User getOrCreateUnregisteredUser(String email,String name) | |
362 throws ModelException | |
363 { | |
364 return UserImpl.getOrCreateUnregisteredUser(this,email,name); | |
365 } | |
366 | |
367 public User getOrCreateUser(String email) { | |
368 return UserImpl.getOrCreateUser(this,email); | |
369 } | |
370 | |
371 public User getOrCreateUser(String email,String name) { | |
372 UserImpl user = getUserImplFromEmail(email); | |
373 if( user==null ) { | |
374 user = UserImpl.createGhost(this,email); | |
375 user.setNameLike(name,false); | |
376 user.insert(); | |
377 } | |
378 return user; | |
379 } | |
380 | |
381 public String newRegistration(String email,String password,String name,String nextUrl) | |
382 throws ModelException | |
383 { | |
384 return UserImpl.createUser(this,email,password,name).newRegistration(nextUrl); | |
385 } | |
386 | |
387 public User getRegistration(String registrationKey) | |
388 throws ModelException | |
389 { | |
390 return UserImpl.getRegistration(this,registrationKey); | |
391 } | |
392 | |
393 public List<User> getUsersByNodeCount(int i, int n, String cnd) { | |
394 try { | |
395 List<User> list = new ArrayList<User>(); | |
396 Connection con = db().getConnection(); | |
397 PreparedStatement stmt1 = con.prepareStatement( | |
398 "select *, (select count(*) from node where user_.user_id=node.owner_id) as n" | |
399 +" from user_" | |
400 + (cnd == null? "" : " where " + cnd) | |
401 +" order by n desc" | |
402 +" limit ? offset ?" | |
403 ); | |
404 stmt1.setInt(1,n); | |
405 stmt1.setInt(2,i); | |
406 ResultSet rs1 = stmt1.executeQuery(); | |
407 while( rs1.next() ) { | |
408 UserImpl user = UserImpl.getUser(siteKey,rs1); | |
409 user.setNodeCount( rs1.getInt("n") ); | |
410 list.add(user); | |
411 } | |
412 rs1.close(); | |
413 stmt1.close(); | |
414 con.close(); | |
415 return list; | |
416 } catch(SQLException e) { | |
417 throw new RuntimeException(e); | |
418 } | |
419 } | |
420 | |
421 public int getUserCount(String cnd) { | |
422 try { | |
423 Connection con = db().getConnection(); | |
424 Statement stmt = con.createStatement(); | |
425 ResultSet rs = stmt.executeQuery( | |
426 "select count(*) as n from user_" + | |
427 (cnd == null? "" : " where " + cnd) | |
428 ); | |
429 rs.next(); | |
430 int n = rs.getInt("n"); | |
431 rs.close(); | |
432 stmt.close(); | |
433 con.close(); | |
434 return n; | |
435 } catch(SQLException e) { | |
436 throw new RuntimeException(e); | |
437 } | |
438 } | |
439 | |
440 | |
441 public void deleteRootNode() throws ModelException { | |
442 if( !db().isInTransaction() ) { | |
443 db().beginTransaction(); | |
444 try { | |
445 SiteImpl site = DbUtils.getGoodCopy(this); | |
446 site.deleteRootNode(); | |
447 db().commitTransaction(); | |
448 } finally { | |
449 db().endTransaction(); | |
450 } | |
451 return; | |
452 } | |
453 NodeImpl oldRoot = getRootNodeImpl(); | |
454 List<NodeImpl> children = oldRoot.getChildrenImpl(null).asList(); | |
455 if( children.size() != 1 ) | |
456 throw ModelException.newInstance("cant_delete_root","Root node must have exactly one child"); | |
457 NodeImpl child = children.get(0); | |
458 child.makeRoot(); | |
459 setRoot(child); | |
460 oldRoot.getDbRecord().delete(); | |
461 } | |
462 | |
463 public Person getAnonymous(String cookie, String name) { | |
464 return new Anonymous(this,cookie,name); | |
465 } | |
466 | |
467 public String newAnonymousCookie() { | |
468 return Anonymous.newCookie(this); | |
469 } | |
470 | |
471 public Person getPerson(String id) { | |
472 int i = id.indexOf(Anonymous.SEPERATOR); | |
473 if( i == -1 ) | |
474 return getUser(Long.parseLong(id)); | |
475 String cookie = id.substring(0,i); | |
476 String name = id.substring(i+1); | |
477 if( name.length() == 0 ) | |
478 name = null; | |
479 return getAnonymous(cookie,name); | |
480 } | |
481 | |
482 | |
483 public void addTag(Node node,User user,String label) { | |
484 TagImpl.addTag(this,node,user,label); | |
485 uncacheTags(node,user); | |
486 } | |
487 | |
488 public void deleteTags(String sqlCondition) { | |
489 TagImpl.deleteTags( siteKey, sqlCondition ); | |
490 } | |
491 | |
492 private static String tagSql(Node node,User user,String sqlCondition) { | |
493 StringBuilder sb = new StringBuilder(); | |
494 if( node == null ) | |
495 sb.append( "node_id is null" ); | |
496 else | |
497 sb.append( "node_id=" ).append( node.getId() ); | |
498 sb.append( " and " ); | |
499 if( user == null ) | |
500 sb.append( "user_id is null" ); | |
501 else | |
502 sb.append( "user_id=" ).append( user.getId() ); | |
503 sb.append( " and " ).append( sqlCondition ); | |
504 return sb.toString(); | |
505 } | |
506 | |
507 public void deleteTags(Node node,User user,String sqlCondition) { | |
508 deleteTags( tagSql(node,user,sqlCondition) ); | |
509 uncacheTags(node,user); | |
510 } | |
511 | |
512 private final Memoizer<String,Boolean> tagCache = new Memoizer<String,Boolean>(new Computable<String,Boolean>() { | |
513 public Boolean get(String sqlCondition) { | |
514 return TagImpl.countTags(siteKey,sqlCondition) > 0; | |
515 } | |
516 }); | |
517 | |
518 private Memoizer<String,Boolean> tagCache(Node node,User user) { | |
519 if( user != null ) | |
520 return ((UserImpl)user).tagCache; | |
521 else if( node != null ) | |
522 return ((NodeImpl)node).tagCache; | |
523 else | |
524 return tagCache; | |
525 } | |
526 | |
527 private void uncacheTags(Node node,User user) { | |
528 if( user != null ) | |
529 DbUtils.uncache((UserImpl)user); | |
530 else if( node != null ) | |
531 DbUtils.uncache((NodeImpl)node); | |
532 else | |
533 DbUtils.uncache(this); | |
534 } | |
535 | |
536 public boolean hasTags(Node node,User user,String sqlCondition) { | |
537 return tagCache(node,user).get( tagSql(node,user,sqlCondition) ); | |
538 } | |
539 | |
540 public int countTags(String sqlCondition) { | |
541 return TagImpl.countTags(siteKey,sqlCondition); | |
542 } | |
543 | |
544 public List<String> findTagLabels(String sqlCondition) { | |
545 return TagImpl.findTagLabels(this,sqlCondition); | |
546 } | |
547 | |
548 public List<User> findTagUsers(String sqlCondition) { | |
549 return new ArrayList<User>( UserImpl.getUsers( siteKey, TagImpl.findTagUserIds(this,sqlCondition) ) ); | |
550 } | |
551 | |
552 public List<Long> findTagUserIds(String sqlCondition) { | |
553 return TagImpl.findTagUserIds(this,sqlCondition); | |
554 } | |
555 | |
556 public List<Node> findTagNodes(String sqlCondition) { | |
557 return new ArrayList<Node>( NodeImpl.getNodes( siteKey, TagImpl.findTagNodeIds(this,sqlCondition) ) ); | |
558 } | |
559 | |
560 public List<Long> findTagNodeIds(String sqlCondition) { | |
561 return TagImpl.findTagNodeIds(this,sqlCondition); | |
562 } | |
563 | |
564 | |
565 private FutureValue<Map<String,Boolean>> modulesEnabled = new FutureValue<Map<String,Boolean>>() { | |
566 protected Map<String,Boolean> compute() { | |
567 Map<String,Boolean> map = new HashMap<String,Boolean>(); | |
568 try { | |
569 Connection con = db().getConnection(); | |
570 Statement stmt = con.createStatement(); | |
571 ResultSet rs = stmt.executeQuery( | |
572 "select module_name, is_enabled from module" | |
573 ); | |
574 while( rs.next() ) { | |
575 String moduleName = rs.getString("module_name"); | |
576 boolean isEnabled = rs.getBoolean("is_enabled"); | |
577 map.put(moduleName,isEnabled); | |
578 } | |
579 rs.close(); | |
580 stmt.close(); | |
581 con.close(); | |
582 } catch(SQLException e) { | |
583 throw new RuntimeException(e); | |
584 } | |
585 if( map.isEmpty() ) | |
586 map = Collections.emptyMap(); | |
587 return map; | |
588 } | |
589 }; | |
590 | |
591 public boolean isModuleEnabled(String moduleName) { | |
592 Boolean b = modulesEnabled.get().get(moduleName); | |
593 return b != null ? b : ModuleManager.isEnabledByDefault(moduleName); | |
594 } | |
595 | |
596 public void setModuleEnabled(String moduleName,boolean isEnabled) { | |
597 try { | |
598 Connection con = db().getConnection(); | |
599 if( isEnabled == ModuleManager.isEnabledByDefault(moduleName) ) { | |
600 PreparedStatement stmt = con.prepareStatement( | |
601 "delete from module where module_name = ?" | |
602 ); | |
603 stmt.setString( 1, moduleName ); | |
604 stmt.executeUpdate(); | |
605 stmt.close(); | |
606 } else if( modulesEnabled.get().get(moduleName) == null ) { | |
607 PreparedStatement stmt = con.prepareStatement( | |
608 "insert into module (module_name,is_enabled) values (?,?)" | |
609 ); | |
610 stmt.setString( 1, moduleName ); | |
611 stmt.setBoolean( 2, isEnabled ); | |
612 stmt.executeUpdate(); | |
613 stmt.close(); | |
614 } else { | |
615 PreparedStatement stmt = con.prepareStatement( | |
616 "update module set is_enabled = ? where module_name = ?" | |
617 ); | |
618 stmt.setBoolean( 1, isEnabled ); | |
619 stmt.setString( 2, moduleName ); | |
620 stmt.executeUpdate(); | |
621 stmt.close(); | |
622 } | |
623 con.close(); | |
624 } catch(SQLException e) { | |
625 throw new RuntimeException(e); | |
626 } | |
627 record.update(); // uncache and fire update listeners | |
628 } | |
629 | |
630 private FutureValue<String> config = new FutureValue<String>() { | |
631 protected String compute() { | |
632 StringBuilder tweak = new StringBuilder(); | |
633 final Set<String> names = new HashSet<String>(); | |
634 try { | |
635 Connection con = db().getConnection(); | |
636 Statement stmt = con.createStatement(); | |
637 ResultSet rs = stmt.executeQuery( | |
638 "select name, naml from configuration" | |
639 ); | |
640 while( rs.next() ) { | |
641 names.add( rs.getString("name") ); | |
642 tweak.append(rs.getString("naml")); | |
643 tweak.append("\n\n"); | |
644 } | |
645 rs.close(); | |
646 stmt.close(); | |
647 con.close(); | |
648 } catch(SQLException e) { | |
649 throw new RuntimeException(e); | |
650 } | |
651 if( !names.isEmpty() ) { | |
652 Executors.executeSometime(new Runnable(){ | |
653 public void run() { | |
654 boolean didDelete = false; | |
655 for( String name : names ) { | |
656 if( !isValidConfiguration(name) ) { | |
657 deleteConfiguration(name); | |
658 didDelete = true; | |
659 logger.error("deleted invalid config: "+name); | |
660 } | |
661 } | |
662 if( didDelete ) | |
663 update(); | |
664 } | |
665 }); | |
666 } | |
667 return tweak.toString(); | |
668 } | |
669 }; | |
670 | |
671 public String getConfigurationTweak() { | |
672 return config.get(); | |
673 } | |
674 | |
675 private volatile FutureValue<Map<String,String>> tweaks = newTweaks(); | |
676 | |
677 private FutureValue<Map<String,String>> newTweaks() { | |
678 return new FutureValue<Map<String,String>>() { | |
679 protected Map<String,String> compute() { | |
680 Map<String,String> map = new HashMap<String,String>(); | |
681 try { | |
682 Connection con = db().getConnection(); | |
683 Statement stmt = con.createStatement(); | |
684 ResultSet rs = stmt.executeQuery( | |
685 "select tweak_name, content from tweak" | |
686 ); | |
687 while( rs.next() ) { | |
688 String tweakName = rs.getString("tweak_name"); | |
689 String content = rs.getString("content"); | |
690 map.put(tweakName,content); | |
691 } | |
692 rs.close(); | |
693 stmt.close(); | |
694 con.close(); | |
695 } catch(SQLException e) { | |
696 throw new RuntimeException(e); | |
697 } | |
698 return CollectionUtils.optimizeMap(map); | |
699 } | |
700 }; | |
701 } | |
702 | |
703 public Map<String,String> getCustomTweaks() { | |
704 return tweaks.get(); | |
705 } | |
706 | |
707 public void setCustomTweaks(Map<String,String> tweaks) { | |
708 try { | |
709 Connection con = db().getConnection(); | |
710 try { | |
711 { | |
712 Statement stmt = con.createStatement(); | |
713 stmt.executeUpdate( | |
714 "delete from tweak" | |
715 ); | |
716 stmt.close(); | |
717 } | |
718 { | |
719 PreparedStatement stmt = con.prepareStatement( | |
720 "insert into tweak (tweak_name,content) values (?,?)" | |
721 ); | |
722 for( Map.Entry<String,String> entry : tweaks.entrySet() ) { | |
723 String tweakName = entry.getKey(); | |
724 String content = entry.getValue(); | |
725 stmt.setString( 1, tweakName ); | |
726 stmt.setString( 2, content ); | |
727 stmt.executeUpdate(); | |
728 } | |
729 stmt.close(); | |
730 } | |
731 } finally { | |
732 con.close(); | |
733 } | |
734 DailyNumber.tweaks.inc(); | |
735 } catch(SQLException e) { | |
736 throw new RuntimeException(e); | |
737 } | |
738 this.tweaks = newTweaks(); | |
739 synchronized(tweakLock) { | |
740 this.tweakException = null; | |
741 this.program = null; | |
742 } | |
743 record.update(); // uncache and fire update listeners | |
744 } | |
745 | |
746 public void resetCustomTweaks() { | |
747 setCustomTweaks(Collections.<String,String>emptyMap()); | |
748 } | |
749 | |
750 private static long traceSiteId = Init.get("traceSiteId",0L); | |
751 | |
752 private boolean trace() { | |
753 return getId() == traceSiteId; | |
754 } | |
755 | |
756 public boolean setTweakException(CompileException tweakException) { | |
757 synchronized(tweakLock) { | |
758 if( this.tweakException != null ) { | |
759 if(trace()) logger.error("this.tweakException already set in "+this+" "+System.identityHashCode(this),new Exception(this.tweakException)); | |
760 return false; | |
761 } | |
762 for( StackTraceElement ste : tweakException.stackTrace ) { | |
763 if( ModuleManager.isConfigurationTweak(ste.source) || ModuleManager.isCustomTweak(ste.source) ) { | |
764 logger.debug("tweak exception in "+this,tweakException); | |
765 if(trace()) logger.error("tweak exception in "+this+" "+System.identityHashCode(this),tweakException); | |
766 this.tweakException = tweakException; | |
767 this.program = null; | |
768 return true; | |
769 } | |
770 } | |
771 if(trace()) logger.error("no tweak in stack trace"); | |
772 return false; | |
773 } | |
774 } | |
775 | |
776 public CompileException getTweakException() { | |
777 synchronized(tweakLock) { | |
778 return tweakException; | |
779 } | |
780 } | |
781 | |
782 public void update() { | |
783 record.update(); | |
784 } | |
785 | |
786 public Site getGoodCopy() { | |
787 return DbUtils.getGoodCopy(this); | |
788 } | |
789 | |
790 NodeImpl getNodeImpl(long id) { | |
791 return NodeImpl.getNode(siteKey,id); | |
792 } | |
793 | |
794 public Node getNode(long id) { | |
795 return getNodeImpl(id); | |
796 } | |
797 | |
798 public Node getNode(ResultSet rs) throws SQLException { | |
799 return NodeImpl.getNode(siteKey,rs); | |
800 } | |
801 | |
802 private void check(Collection<? extends Node> nodes) { | |
803 for( Iterator<? extends Node> i = nodes.iterator(); i.hasNext(); ) { | |
804 if( !i.next().getSite().equals(this) ) | |
805 throw new RuntimeException("node from wrong site"); | |
806 } | |
807 } | |
808 | |
809 public Collection<? extends Node> getNodes(Collection<Long> ids) { | |
810 Collection<NodeImpl> nodes = NodeImpl.getNodes(siteKey,ids); | |
811 check(nodes); | |
812 return nodes; | |
813 } | |
814 | |
815 public NodeIterator<? extends Node> getNodeIterator(String sql,DbParamSetter paramSetter) { | |
816 return new CursorNodeIterator( siteKey, sql, paramSetter ); | |
817 } | |
818 | |
819 | |
820 private final Memoizer<String,String> propertyCache = new Memoizer<String,String>(new Computable<String,String>() { | |
821 public String get(String key) { | |
822 try { | |
823 Connection con = db().getConnection(); | |
824 PreparedStatement stmt = con.prepareStatement( | |
825 "select value from site_property where key = ?" | |
826 ); | |
827 stmt.setString( 1, key ); | |
828 ResultSet rs = stmt.executeQuery(); | |
829 try { | |
830 return rs.next() ? rs.getString("value") : null; | |
831 } finally { | |
832 rs.close(); | |
833 stmt.close(); | |
834 con.close(); | |
835 } | |
836 } catch(SQLException e) { | |
837 throw new RuntimeException(e); | |
838 } | |
839 } | |
840 }); | |
841 | |
842 public String getProperty(String key) { | |
843 return propertyCache.get(key); | |
844 } | |
845 | |
846 public void setProperty(String key,String value) { | |
847 try { | |
848 Connection con = db().getConnection(); | |
849 PreparedStatement stmt = con.prepareStatement( | |
850 "delete from site_property where key = ?" | |
851 ); | |
852 stmt.setString( 1, key ); | |
853 stmt.executeUpdate(); | |
854 stmt.close(); | |
855 if( value != null ) { | |
856 stmt = con.prepareStatement( | |
857 "insert into site_property (key,value) values (?,?)" | |
858 ); | |
859 stmt.setString( 1, key ); | |
860 stmt.setString( 2, value ); | |
861 stmt.executeUpdate(); | |
862 stmt.close(); | |
863 } | |
864 con.close(); | |
865 } catch(SQLException e) { | |
866 throw new RuntimeException(e); | |
867 } finally { | |
868 propertyCache.remove(key); | |
869 } | |
870 } | |
871 | |
872 | |
873 public boolean isValidConfiguration(String name) { | |
874 Template template = getTemplate( "is_valid_configuration", | |
875 BasicNamespace.class, NabbleNamespace.class | |
876 ); | |
877 StringWriter sw = new StringWriter(); | |
878 template.run( new TemplatePrintWriter(sw), Collections.<String,Object>singletonMap("config",name), | |
879 new BasicNamespace(template), new NabbleNamespace(this) | |
880 ); | |
881 return Template.booleanValue(sw.toString().trim()); | |
882 } | |
883 | |
884 private void deleteConfiguration(Connection con,String name) throws SQLException { | |
885 PreparedStatement stmt = con.prepareStatement( | |
886 "delete from configuration where name = ?" | |
887 ); | |
888 stmt.setString( 1, name ); | |
889 stmt.executeUpdate(); | |
890 stmt.close(); | |
891 } | |
892 | |
893 public void deleteConfiguration(String name) { | |
894 try { | |
895 Connection con = db().getConnection(); | |
896 deleteConfiguration(con,name); | |
897 con.close(); | |
898 } catch(SQLException e) { | |
899 throw new RuntimeException(e); | |
900 } | |
901 } | |
902 | |
903 public void saveConfiguration(String name,String value,String naml) { | |
904 if( !isValidConfiguration(name) ) | |
905 throw new RuntimeException("invalid configuration: "+name); | |
906 try { | |
907 Connection con = db().getConnection(); | |
908 deleteConfiguration(con,name); | |
909 PreparedStatement stmt = con.prepareStatement( | |
910 "insert into configuration (name,value,naml) values (?,?,?)" | |
911 ); | |
912 stmt.setString( 1, name ); | |
913 stmt.setString( 2, value ); | |
914 stmt.setString( 3, naml ); | |
915 stmt.executeUpdate(); | |
916 stmt.close(); | |
917 con.close(); | |
918 } catch(SQLException e) { | |
919 throw new RuntimeException(e); | |
920 } | |
921 } | |
922 | |
923 public String getConfigurationValue(String name) { | |
924 if( !isValidConfiguration(name) ) | |
925 throw new RuntimeException("invalid configuration: "+name); | |
926 try { | |
927 Connection con = db().getConnection(); | |
928 PreparedStatement stmt = con.prepareStatement( | |
929 "select value from configuration where name = ?" | |
930 ); | |
931 stmt.setString( 1, name ); | |
932 ResultSet rs = stmt.executeQuery(); | |
933 try { | |
934 return rs.next() ? rs.getString("value") : null; | |
935 } finally { | |
936 rs.close(); | |
937 stmt.close(); | |
938 con.close(); | |
939 } | |
940 } catch(SQLException e) { | |
941 throw new RuntimeException(e); | |
942 } | |
943 } | |
944 | |
945 | |
946 public String getNextUrl(String registrationKey) { | |
947 return UserImpl.getNextUrl(siteKey,registrationKey); | |
948 } | |
949 | |
950 public Node newNode(NodeData data) | |
951 throws ModelException | |
952 { | |
953 return new NodeImpl(this,data); | |
954 } | |
955 | |
956 public Collection<Node> cacheLastNodes(Collection<Node> nodes) { | |
957 Collection<LongKey> keys = new ArrayList<LongKey>(); | |
958 for( Node n : nodes ) { | |
959 NodeImpl node = (NodeImpl)n; | |
960 keys.add( new LongKey(node.getLastNodeId()) ); | |
961 } | |
962 Map<LongKey,NodeImpl> objs = NodeImpl.table(siteKey).findByPrimaryKey(keys); | |
963 return new ArrayList<Node>(objs.values()); | |
964 } | |
965 | |
966 | |
967 public void addTask(String task) { | |
968 siteKey.addTask(task); | |
969 } | |
970 | |
971 | |
972 | |
973 private Map<ExtensionFactory<Site,?>,Object> extensionMap; | |
974 | |
975 private synchronized Map<ExtensionFactory<Site, ?>, Object> getExtensionMap() { | |
976 if (extensionMap == null) | |
977 extensionMap = new HashMap<ExtensionFactory<Site, ?>, Object>(); | |
978 return extensionMap; | |
979 } | |
980 | |
981 public <T> T getExtension(ExtensionFactory<Site,T> factory) { | |
982 synchronized(getExtensionMap()) { | |
983 Object obj = extensionMap.get(factory); | |
984 if( obj == null ) { | |
985 obj = factory.construct(this); | |
986 if( obj != null ) | |
987 extensionMap.put(factory,obj); | |
988 } | |
989 return factory.extensionClass().cast(obj); | |
990 } | |
991 } | |
992 | |
993 private static Collection<ExtensionFactory<Site,?>> extensionFactories = new CopyOnWriteArrayList<ExtensionFactory<Site,?>>(); | |
994 | |
995 static <T> void addExtensionFactory(ExtensionFactory<Site,T> factory) { | |
996 extensionFactories.add(factory); | |
997 Db.clearCache(); | |
998 } | |
999 | |
1000 | |
1001 | |
1002 public Site getSite() { | |
1003 return this; | |
1004 } | |
1005 | |
1006 public long getSourceId() { | |
1007 throw new UnsupportedOperationException(); | |
1008 } | |
1009 | |
1010 public Message.SourceType getMessageSourceType() { | |
1011 return Message.SourceType.SITE; | |
1012 } | |
1013 | |
1014 | |
1015 public void delete() { | |
1016 if( getRootNode().getOwner() instanceof User ) { | |
1017 File file = backup(); | |
1018 // Don't sent the deletion email if the site has only one node | |
1019 if (getRootNode().getDescendantCount() > 1) { | |
1020 Template template = getTemplate( "site deletion email", | |
1021 BasicNamespace.class, NabbleNamespace.class | |
1022 ); | |
1023 Map<String,Object> params = new HashMap<String,Object>(); | |
1024 params.put("file",file.getName()); | |
1025 template.run( TemplatePrintWriter.NULL, params, | |
1026 new BasicNamespace(template), | |
1027 new NabbleNamespace(this) | |
1028 ); | |
1029 } | |
1030 } | |
1031 kill(); | |
1032 } | |
1033 | |
1034 public void kill() { | |
1035 if( !db().isInTransaction() ) { | |
1036 db().beginTransaction(); | |
1037 try { | |
1038 SiteImpl site = DbUtils.getGoodCopy(SiteImpl.this); | |
1039 site.kill(); | |
1040 db().commitTransaction(); | |
1041 } finally { | |
1042 db().endTransaction(); | |
1043 } | |
1044 return; | |
1045 } | |
1046 SiteGlobal siteGlobal = siteGlobal(); | |
1047 if( siteGlobal == null ) | |
1048 throw new NullPointerException("siteGlobal not found for "+siteKey); | |
1049 try { | |
1050 Connection con = Db.dbPostgres().getConnection(); | |
1051 Statement stmt = con.createStatement(); | |
1052 stmt.executeUpdate( | |
1053 "drop schema " + siteKey.schema() + " cascade" | |
1054 ); | |
1055 DbUtils.uncache(this); | |
1056 stmt.close(); | |
1057 con.close(); | |
1058 } catch(SQLException e) { | |
1059 throw new RuntimeException(e); | |
1060 } | |
1061 siteGlobal.getDbRecord().delete(); | |
1062 } | |
1063 | |
1064 private static final String SALT = "zDf3s"; | |
2
abe0694e9849
replace local_dir with home_dir
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
1065 private static final File schemaDir = new File((String)Init.get("home_dir")+"local/schemas/"); |
0 | 1066 |
1067 private static File getBackupFile(long siteId) { | |
1068 int hash = Math.abs(( SALT + siteId ).hashCode()); | |
1069 String filename = "site_"+siteId+"_"+hash+".zip"; | |
1070 return new File(schemaDir,filename); | |
1071 } | |
1072 | |
1073 public File backup() { | |
1074 File file = getBackupFile(getId()); | |
1075 backup(file); | |
1076 return file; | |
1077 } | |
1078 | |
1079 public void backup(String filename) { | |
1080 backup( new File(filename) ); | |
1081 } | |
1082 | |
1083 private void backup(File file) { | |
1084 file.delete(); | |
1085 ZipBackup backup = new ZipBackup( file, Db.completeUrl ); | |
1086 backup.dump( Collections.singleton(siteKey.schema()), DataFilter.ALL_DATA ); | |
1087 } | |
1088 | |
1089 static final DataFilter SCHEMA_DATA = new DataFilter() { | |
1090 public boolean dumpData(String schema,String tableName) { | |
1091 return tableName.equals("version"); | |
1092 } | |
1093 }; | |
1094 | |
1095 public void backupSchema(String filename) { | |
1096 ZipBackup backup = new ZipBackup( new File(filename), Db.completeUrl ); | |
1097 backup.dump( Collections.singleton(siteKey.schema()), SCHEMA_DATA ); | |
1098 } | |
1099 // in beanshell, I do: | |
1100 // s = ModelHome.getSite(2) | |
1101 // s.backupSchema("/Users/Franklin/hg/nabble/src/nabble/data/site.schema") | |
1102 | |
1103 } |