Mercurial Hosting > nabble
diff src/nabble/model/SiteImpl.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children | abe0694e9849 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/nabble/model/SiteImpl.java Thu Mar 21 19:15:52 2019 -0600 @@ -0,0 +1,1103 @@ +package nabble.model; + +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.DbRecord; +import fschmidt.db.DbTable; +import fschmidt.db.DbUtils; +import fschmidt.db.Listener; +import fschmidt.db.ListenerList; +import fschmidt.db.LongKey; +import fschmidt.db.NoKey; +import fschmidt.db.NoKeySetter; +import fschmidt.util.java.CollectionUtils; +import fschmidt.util.java.Computable; +import fschmidt.util.java.FutureValue; +import fschmidt.util.java.Memoizer; +import fschmidt.util.java.SimpleCache; +import jdbcpgbackup.DataFilter; +import jdbcpgbackup.ZipBackup; +import nabble.model.export.NodeData; +import nabble.modules.ModuleManager; +import nabble.naml.compiler.CompileException; +import nabble.naml.compiler.Module; +import nabble.naml.compiler.Program; +import nabble.naml.compiler.StackTraceElement; +import nabble.naml.compiler.Template; +import nabble.naml.compiler.TemplatePrintWriter; +import nabble.naml.namespaces.BasicNamespace; +import nabble.view.web.template.NabbleNamespace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + + +final class SiteImpl implements Site { + private static final Logger logger = LoggerFactory.getLogger(SiteImpl.class); + + final SiteKey siteKey; + private final DbRecord<NoKey,SiteImpl> record; + private long rootNodeId; + private NodeImpl rootNode = null; + + private final Object tweakLock = new Object(); + private CompileException tweakException = null; + private Program program = null; + private final Date whenCreated; + + SiteImpl(SiteKey siteKey) { + this.siteKey = siteKey; + record = table(siteKey).newRecord(this); + record.fields().put( "root_node_id", 0L ); + whenCreated = new Date(); + } + + void setRoot(NodeImpl node) { + if( !node.isInDb() ) + throw new RuntimeException("node must be in db"); + this.rootNodeId = node.getId(); + record.fields().put( "root_node_id", rootNodeId ); + this.rootNode = node; + record.update(); + } + + private SiteImpl(SiteKey siteKey,NoKey key,ResultSet rs) + throws SQLException + { + this.siteKey = siteKey; + record = table(siteKey).newRecord(this,key); + rootNodeId = rs.getLong("root_node_id"); + whenCreated = rs.getTimestamp("when_created"); + for( ExtensionFactory<Site,?> factory : extensionFactories ) { + Object obj = factory.construct(this,rs); + if( obj != null ) + getExtensionMap().put(factory,obj); + } + } + + public DbRecord<NoKey,SiteImpl> getDbRecord() { + return record; + } + + private DbTable<NoKey,SiteImpl> table() { + return record.getDbTable(); + } + + private DbDatabase db() { + return table().getDbDatabase(); + } + + public DbDatabase getDb() { + return siteKey.getDb(); + } + + public long getId() { + return siteKey.getId(); + } +/* + private void calcBaseUrl() { + siteGlobal().calcBaseUrl(); + } +*/ + long getRootNodeId() { + return rootNodeId; + } + + NodeImpl getRootNodeImpl() { + if( DbUtils.isStale(rootNode) ) { + rootNode = NodeImpl.getNode(siteKey,rootNodeId); + } + return rootNode; + } + + public Node getRootNode() { + return getRootNodeImpl(); + } + + public Date getWhenCreated() { + return whenCreated; + } + + /** To be called from the shell */ + public void setWhenCreated(int day, int month, int year) { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.DAY_OF_MONTH, day); + cal.set(Calendar.MONTH, month); + cal.set(Calendar.YEAR, year); + DbRecord<NoKey,?> record = getDbRecord(); + record.fields().put("when_created", cal.getTime()); + record.update(); + } + + SiteGlobal siteGlobal() { + return siteKey.siteGlobal(); + } + + @Override public boolean equals(Object obj) { + return this==obj || obj instanceof SiteImpl && record.isInDb() && ((SiteImpl)obj).getId()==getId(); + } + + @Override public int hashCode() { + return (int)getId(); + } + + public Program getProgram() { + synchronized(tweakLock) { + if( program == null ) { + if(trace()) logger.error("getting Program for "+this+" "+System.identityHashCode(this)+" tweakException="+tweakException); + List<Module> modules = ModuleManager.getModules(SiteImpl.this); + program = Program.getInstance(modules); + } + return program; + } + } + + public Template getTemplate(String templateName,Class... base) { + synchronized(tweakLock) { + try { + return getProgram().getTemplate(templateName,base); + } catch(CompileException e) { + if( setTweakException(e) ) { + return getTemplate(templateName,base); + } + throw new RuntimeException(""+this+" "+System.identityHashCode(this),e); + } + } + } + + public void setCustomDomain(String customDomain) { + siteGlobal().setCustomDomain(customDomain); + } + + public String getCustomDomain() { + return siteGlobal().getCustomDomain(); + } + + + public String getBaseUrl() { + return siteGlobal().getBaseUrl(); + } + + List<UserImpl> getPosters() { + try { + Connection con = db().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select * from user_ where user_id in (" + +"select distinct owner_id from node where redirect is null" + +")" + ); + try { + return UserImpl.getUsers(siteKey,stmt); + } finally { + stmt.close(); + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + public List<User> getUsers(String cnd) { + List<User> list = new ArrayList<User>(); + getUserImpls(list, cnd); + return list; + } + + List<UserImpl> getUserImpls(String cnd) { + List<UserImpl> list = new ArrayList<UserImpl>(); + getUserImpls(list, cnd); + return list; + } + + void getUserImpls(List<? super UserImpl> list, String cnd) { + try { + Connection con = db().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select * from user_" + + (cnd == null? "" : " where " + cnd) + ); + try { + UserImpl.getUsers(siteKey,stmt,list); + } finally { + stmt.close(); + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + public int getActivity() { + return siteGlobal().getActivity(); + } + + public void setActivity(int activity) { + siteGlobal().setActivity(activity); + } + + public boolean isEmbarrassing() { + return siteGlobal().isEmbarrassing(); + } + + public void setEmbarrassing(boolean isEmbarrassing) { + siteGlobal().setEmbarrassing(isEmbarrassing); + } + + + public String toString() { + return "site-"+getId(); + } + + static final ListenerList<SiteImpl> postUpdateListeners = new ListenerList<SiteImpl>(); + private static final ListenerList<SiteImpl> postDeleteListeners = new ListenerList<SiteImpl>(); + private static final ListenerList<SiteImpl> preInsertListeners = new ListenerList<SiteImpl>(); + private static final ListenerList<SiteImpl> preUpdateListeners = new ListenerList<SiteImpl>(); + + 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>>() { + public DbTable<NoKey,SiteImpl> get(SiteKey siteKey) { + DbDatabase db = siteKey.getDb(); + final long siteId = siteKey.getId(); + DbTable<NoKey,SiteImpl> table = db.newTable("site",NoKeySetter.INSTANCE + , new DbObjectFactory<NoKey,SiteImpl>() { + public SiteImpl makeDbObject(NoKey key,ResultSet rs,String tableName) + throws SQLException + { + SiteKey siteKey = SiteKey.getInstance(siteId); + return new SiteImpl(siteKey,key,rs); + } + } + ); + table.getPreInsertListeners().add(preInsertListeners); + table.getPreUpdateListeners().add(preUpdateListeners); + table.getPostUpdateListeners().add(postUpdateListeners); + table.getPostDeleteListeners().add(postDeleteListeners); + return table; + } + }); + + private static DbTable<NoKey,SiteImpl> table(SiteKey siteKey) { + return tables.get(siteKey); + } + + static SiteImpl getSite(SiteKey siteKey,long siteId) { + return table(siteKey).findByPrimaryKey(NoKey.INSTANCE); + } + + static SiteImpl getSite(SiteKey siteKey,ResultSet rs) + throws SQLException + { + return table(siteKey).getDbObject(rs); + } + + public Date getDeleteDate() { + return siteGlobal().getDeleteDate(); + } + + public void clearDeleteDate() { + siteGlobal().clearDeleteDate(); + } + + + + + + + + static void addChangeListener(final Listener<? super SiteImpl> listener) { + postUpdateListeners.add(listener); + postDeleteListeners.add(listener); + } + + static void addPreChangeListener(final Listener<? super SiteImpl> listener) { + preInsertListeners.add(listener); + preUpdateListeners.add(listener); + } + + + public User getUser(long id) { + return getUserImpl(id); + } + + UserImpl getUserImpl(long id) { + return UserImpl.getUser(siteKey,id); + } + + public User getUserFromEmail(String email) { + return getUserImplFromEmail(email); + } + + UserImpl getUserImplFromEmail(String email) { + return UserImpl.getUserFromEmail(this,email); + } + + public User getUserFromName(String name) { + return getUserImplFromName(name); + } + + UserImpl getUserImplFromName(String name) { + return UserImpl.getUserFromName(this,name); + } + + public User getOrCreateUnregisteredUser(String email,String name) + throws ModelException + { + return UserImpl.getOrCreateUnregisteredUser(this,email,name); + } + + public User getOrCreateUser(String email) { + return UserImpl.getOrCreateUser(this,email); + } + + public User getOrCreateUser(String email,String name) { + UserImpl user = getUserImplFromEmail(email); + if( user==null ) { + user = UserImpl.createGhost(this,email); + user.setNameLike(name,false); + user.insert(); + } + return user; + } + + public String newRegistration(String email,String password,String name,String nextUrl) + throws ModelException + { + return UserImpl.createUser(this,email,password,name).newRegistration(nextUrl); + } + + public User getRegistration(String registrationKey) + throws ModelException + { + return UserImpl.getRegistration(this,registrationKey); + } + + public List<User> getUsersByNodeCount(int i, int n, String cnd) { + try { + List<User> list = new ArrayList<User>(); + Connection con = db().getConnection(); + PreparedStatement stmt1 = con.prepareStatement( + "select *, (select count(*) from node where user_.user_id=node.owner_id) as n" + +" from user_" + + (cnd == null? "" : " where " + cnd) + +" order by n desc" + +" limit ? offset ?" + ); + stmt1.setInt(1,n); + stmt1.setInt(2,i); + ResultSet rs1 = stmt1.executeQuery(); + while( rs1.next() ) { + UserImpl user = UserImpl.getUser(siteKey,rs1); + user.setNodeCount( rs1.getInt("n") ); + list.add(user); + } + rs1.close(); + stmt1.close(); + con.close(); + return list; + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + public int getUserCount(String cnd) { + try { + Connection con = db().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select count(*) as n from user_" + + (cnd == null? "" : " where " + cnd) + ); + rs.next(); + int n = rs.getInt("n"); + rs.close(); + stmt.close(); + con.close(); + return n; + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + + public void deleteRootNode() throws ModelException { + if( !db().isInTransaction() ) { + db().beginTransaction(); + try { + SiteImpl site = DbUtils.getGoodCopy(this); + site.deleteRootNode(); + db().commitTransaction(); + } finally { + db().endTransaction(); + } + return; + } + NodeImpl oldRoot = getRootNodeImpl(); + List<NodeImpl> children = oldRoot.getChildrenImpl(null).asList(); + if( children.size() != 1 ) + throw ModelException.newInstance("cant_delete_root","Root node must have exactly one child"); + NodeImpl child = children.get(0); + child.makeRoot(); + setRoot(child); + oldRoot.getDbRecord().delete(); + } + + public Person getAnonymous(String cookie, String name) { + return new Anonymous(this,cookie,name); + } + + public String newAnonymousCookie() { + return Anonymous.newCookie(this); + } + + public Person getPerson(String id) { + int i = id.indexOf(Anonymous.SEPERATOR); + if( i == -1 ) + return getUser(Long.parseLong(id)); + String cookie = id.substring(0,i); + String name = id.substring(i+1); + if( name.length() == 0 ) + name = null; + return getAnonymous(cookie,name); + } + + + public void addTag(Node node,User user,String label) { + TagImpl.addTag(this,node,user,label); + uncacheTags(node,user); + } + + public void deleteTags(String sqlCondition) { + TagImpl.deleteTags( siteKey, sqlCondition ); + } + + private static String tagSql(Node node,User user,String sqlCondition) { + StringBuilder sb = new StringBuilder(); + if( node == null ) + sb.append( "node_id is null" ); + else + sb.append( "node_id=" ).append( node.getId() ); + sb.append( " and " ); + if( user == null ) + sb.append( "user_id is null" ); + else + sb.append( "user_id=" ).append( user.getId() ); + sb.append( " and " ).append( sqlCondition ); + return sb.toString(); + } + + public void deleteTags(Node node,User user,String sqlCondition) { + deleteTags( tagSql(node,user,sqlCondition) ); + uncacheTags(node,user); + } + + private final Memoizer<String,Boolean> tagCache = new Memoizer<String,Boolean>(new Computable<String,Boolean>() { + public Boolean get(String sqlCondition) { + return TagImpl.countTags(siteKey,sqlCondition) > 0; + } + }); + + private Memoizer<String,Boolean> tagCache(Node node,User user) { + if( user != null ) + return ((UserImpl)user).tagCache; + else if( node != null ) + return ((NodeImpl)node).tagCache; + else + return tagCache; + } + + private void uncacheTags(Node node,User user) { + if( user != null ) + DbUtils.uncache((UserImpl)user); + else if( node != null ) + DbUtils.uncache((NodeImpl)node); + else + DbUtils.uncache(this); + } + + public boolean hasTags(Node node,User user,String sqlCondition) { + return tagCache(node,user).get( tagSql(node,user,sqlCondition) ); + } + + public int countTags(String sqlCondition) { + return TagImpl.countTags(siteKey,sqlCondition); + } + + public List<String> findTagLabels(String sqlCondition) { + return TagImpl.findTagLabels(this,sqlCondition); + } + + public List<User> findTagUsers(String sqlCondition) { + return new ArrayList<User>( UserImpl.getUsers( siteKey, TagImpl.findTagUserIds(this,sqlCondition) ) ); + } + + public List<Long> findTagUserIds(String sqlCondition) { + return TagImpl.findTagUserIds(this,sqlCondition); + } + + public List<Node> findTagNodes(String sqlCondition) { + return new ArrayList<Node>( NodeImpl.getNodes( siteKey, TagImpl.findTagNodeIds(this,sqlCondition) ) ); + } + + public List<Long> findTagNodeIds(String sqlCondition) { + return TagImpl.findTagNodeIds(this,sqlCondition); + } + + + private FutureValue<Map<String,Boolean>> modulesEnabled = new FutureValue<Map<String,Boolean>>() { + protected Map<String,Boolean> compute() { + Map<String,Boolean> map = new HashMap<String,Boolean>(); + try { + Connection con = db().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select module_name, is_enabled from module" + ); + while( rs.next() ) { + String moduleName = rs.getString("module_name"); + boolean isEnabled = rs.getBoolean("is_enabled"); + map.put(moduleName,isEnabled); + } + rs.close(); + stmt.close(); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + if( map.isEmpty() ) + map = Collections.emptyMap(); + return map; + } + }; + + public boolean isModuleEnabled(String moduleName) { + Boolean b = modulesEnabled.get().get(moduleName); + return b != null ? b : ModuleManager.isEnabledByDefault(moduleName); + } + + public void setModuleEnabled(String moduleName,boolean isEnabled) { + try { + Connection con = db().getConnection(); + if( isEnabled == ModuleManager.isEnabledByDefault(moduleName) ) { + PreparedStatement stmt = con.prepareStatement( + "delete from module where module_name = ?" + ); + stmt.setString( 1, moduleName ); + stmt.executeUpdate(); + stmt.close(); + } else if( modulesEnabled.get().get(moduleName) == null ) { + PreparedStatement stmt = con.prepareStatement( + "insert into module (module_name,is_enabled) values (?,?)" + ); + stmt.setString( 1, moduleName ); + stmt.setBoolean( 2, isEnabled ); + stmt.executeUpdate(); + stmt.close(); + } else { + PreparedStatement stmt = con.prepareStatement( + "update module set is_enabled = ? where module_name = ?" + ); + stmt.setBoolean( 1, isEnabled ); + stmt.setString( 2, moduleName ); + stmt.executeUpdate(); + stmt.close(); + } + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + record.update(); // uncache and fire update listeners + } + + private FutureValue<String> config = new FutureValue<String>() { + protected String compute() { + StringBuilder tweak = new StringBuilder(); + final Set<String> names = new HashSet<String>(); + try { + Connection con = db().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select name, naml from configuration" + ); + while( rs.next() ) { + names.add( rs.getString("name") ); + tweak.append(rs.getString("naml")); + tweak.append("\n\n"); + } + rs.close(); + stmt.close(); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + if( !names.isEmpty() ) { + Executors.executeSometime(new Runnable(){ + public void run() { + boolean didDelete = false; + for( String name : names ) { + if( !isValidConfiguration(name) ) { + deleteConfiguration(name); + didDelete = true; + logger.error("deleted invalid config: "+name); + } + } + if( didDelete ) + update(); + } + }); + } + return tweak.toString(); + } + }; + + public String getConfigurationTweak() { + return config.get(); + } + + private volatile FutureValue<Map<String,String>> tweaks = newTweaks(); + + private FutureValue<Map<String,String>> newTweaks() { + return new FutureValue<Map<String,String>>() { + protected Map<String,String> compute() { + Map<String,String> map = new HashMap<String,String>(); + try { + Connection con = db().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select tweak_name, content from tweak" + ); + while( rs.next() ) { + String tweakName = rs.getString("tweak_name"); + String content = rs.getString("content"); + map.put(tweakName,content); + } + rs.close(); + stmt.close(); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + return CollectionUtils.optimizeMap(map); + } + }; + } + + public Map<String,String> getCustomTweaks() { + return tweaks.get(); + } + + public void setCustomTweaks(Map<String,String> tweaks) { + try { + Connection con = db().getConnection(); + try { + { + Statement stmt = con.createStatement(); + stmt.executeUpdate( + "delete from tweak" + ); + stmt.close(); + } + { + PreparedStatement stmt = con.prepareStatement( + "insert into tweak (tweak_name,content) values (?,?)" + ); + for( Map.Entry<String,String> entry : tweaks.entrySet() ) { + String tweakName = entry.getKey(); + String content = entry.getValue(); + stmt.setString( 1, tweakName ); + stmt.setString( 2, content ); + stmt.executeUpdate(); + } + stmt.close(); + } + } finally { + con.close(); + } + DailyNumber.tweaks.inc(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + this.tweaks = newTweaks(); + synchronized(tweakLock) { + this.tweakException = null; + this.program = null; + } + record.update(); // uncache and fire update listeners + } + + public void resetCustomTweaks() { + setCustomTweaks(Collections.<String,String>emptyMap()); + } + + private static long traceSiteId = Init.get("traceSiteId",0L); + + private boolean trace() { + return getId() == traceSiteId; + } + + public boolean setTweakException(CompileException tweakException) { + synchronized(tweakLock) { + if( this.tweakException != null ) { + if(trace()) logger.error("this.tweakException already set in "+this+" "+System.identityHashCode(this),new Exception(this.tweakException)); + return false; + } + for( StackTraceElement ste : tweakException.stackTrace ) { + if( ModuleManager.isConfigurationTweak(ste.source) || ModuleManager.isCustomTweak(ste.source) ) { + logger.debug("tweak exception in "+this,tweakException); + if(trace()) logger.error("tweak exception in "+this+" "+System.identityHashCode(this),tweakException); + this.tweakException = tweakException; + this.program = null; + return true; + } + } + if(trace()) logger.error("no tweak in stack trace"); + return false; + } + } + + public CompileException getTweakException() { + synchronized(tweakLock) { + return tweakException; + } + } + + public void update() { + record.update(); + } + + public Site getGoodCopy() { + return DbUtils.getGoodCopy(this); + } + + NodeImpl getNodeImpl(long id) { + return NodeImpl.getNode(siteKey,id); + } + + public Node getNode(long id) { + return getNodeImpl(id); + } + + public Node getNode(ResultSet rs) throws SQLException { + return NodeImpl.getNode(siteKey,rs); + } + + private void check(Collection<? extends Node> nodes) { + for( Iterator<? extends Node> i = nodes.iterator(); i.hasNext(); ) { + if( !i.next().getSite().equals(this) ) + throw new RuntimeException("node from wrong site"); + } + } + + public Collection<? extends Node> getNodes(Collection<Long> ids) { + Collection<NodeImpl> nodes = NodeImpl.getNodes(siteKey,ids); + check(nodes); + return nodes; + } + + public NodeIterator<? extends Node> getNodeIterator(String sql,DbParamSetter paramSetter) { + return new CursorNodeIterator( siteKey, sql, paramSetter ); + } + + + private final Memoizer<String,String> propertyCache = new Memoizer<String,String>(new Computable<String,String>() { + public String get(String key) { + try { + Connection con = db().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select value from site_property where key = ?" + ); + stmt.setString( 1, key ); + ResultSet rs = stmt.executeQuery(); + try { + return rs.next() ? rs.getString("value") : null; + } finally { + rs.close(); + stmt.close(); + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + }); + + public String getProperty(String key) { + return propertyCache.get(key); + } + + public void setProperty(String key,String value) { + try { + Connection con = db().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "delete from site_property where key = ?" + ); + stmt.setString( 1, key ); + stmt.executeUpdate(); + stmt.close(); + if( value != null ) { + stmt = con.prepareStatement( + "insert into site_property (key,value) values (?,?)" + ); + stmt.setString( 1, key ); + stmt.setString( 2, value ); + stmt.executeUpdate(); + stmt.close(); + } + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } finally { + propertyCache.remove(key); + } + } + + + public boolean isValidConfiguration(String name) { + Template template = getTemplate( "is_valid_configuration", + BasicNamespace.class, NabbleNamespace.class + ); + StringWriter sw = new StringWriter(); + template.run( new TemplatePrintWriter(sw), Collections.<String,Object>singletonMap("config",name), + new BasicNamespace(template), new NabbleNamespace(this) + ); + return Template.booleanValue(sw.toString().trim()); + } + + private void deleteConfiguration(Connection con,String name) throws SQLException { + PreparedStatement stmt = con.prepareStatement( + "delete from configuration where name = ?" + ); + stmt.setString( 1, name ); + stmt.executeUpdate(); + stmt.close(); + } + + public void deleteConfiguration(String name) { + try { + Connection con = db().getConnection(); + deleteConfiguration(con,name); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + public void saveConfiguration(String name,String value,String naml) { + if( !isValidConfiguration(name) ) + throw new RuntimeException("invalid configuration: "+name); + try { + Connection con = db().getConnection(); + deleteConfiguration(con,name); + PreparedStatement stmt = con.prepareStatement( + "insert into configuration (name,value,naml) values (?,?,?)" + ); + stmt.setString( 1, name ); + stmt.setString( 2, value ); + stmt.setString( 3, naml ); + stmt.executeUpdate(); + stmt.close(); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + public String getConfigurationValue(String name) { + if( !isValidConfiguration(name) ) + throw new RuntimeException("invalid configuration: "+name); + try { + Connection con = db().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select value from configuration where name = ?" + ); + stmt.setString( 1, name ); + ResultSet rs = stmt.executeQuery(); + try { + return rs.next() ? rs.getString("value") : null; + } finally { + rs.close(); + stmt.close(); + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + + public String getNextUrl(String registrationKey) { + return UserImpl.getNextUrl(siteKey,registrationKey); + } + + public Node newNode(NodeData data) + throws ModelException + { + return new NodeImpl(this,data); + } + + public Collection<Node> cacheLastNodes(Collection<Node> nodes) { + Collection<LongKey> keys = new ArrayList<LongKey>(); + for( Node n : nodes ) { + NodeImpl node = (NodeImpl)n; + keys.add( new LongKey(node.getLastNodeId()) ); + } + Map<LongKey,NodeImpl> objs = NodeImpl.table(siteKey).findByPrimaryKey(keys); + return new ArrayList<Node>(objs.values()); + } + + + public void addTask(String task) { + siteKey.addTask(task); + } + + + + private Map<ExtensionFactory<Site,?>,Object> extensionMap; + + private synchronized Map<ExtensionFactory<Site, ?>, Object> getExtensionMap() { + if (extensionMap == null) + extensionMap = new HashMap<ExtensionFactory<Site, ?>, Object>(); + return extensionMap; + } + + public <T> T getExtension(ExtensionFactory<Site,T> factory) { + synchronized(getExtensionMap()) { + Object obj = extensionMap.get(factory); + if( obj == null ) { + obj = factory.construct(this); + if( obj != null ) + extensionMap.put(factory,obj); + } + return factory.extensionClass().cast(obj); + } + } + + private static Collection<ExtensionFactory<Site,?>> extensionFactories = new CopyOnWriteArrayList<ExtensionFactory<Site,?>>(); + + static <T> void addExtensionFactory(ExtensionFactory<Site,T> factory) { + extensionFactories.add(factory); + Db.clearCache(); + } + + + + public Site getSite() { + return this; + } + + public long getSourceId() { + throw new UnsupportedOperationException(); + } + + public Message.SourceType getMessageSourceType() { + return Message.SourceType.SITE; + } + + + public void delete() { + if( getRootNode().getOwner() instanceof User ) { + File file = backup(); + // Don't sent the deletion email if the site has only one node + if (getRootNode().getDescendantCount() > 1) { + Template template = getTemplate( "site deletion email", + BasicNamespace.class, NabbleNamespace.class + ); + Map<String,Object> params = new HashMap<String,Object>(); + params.put("file",file.getName()); + template.run( TemplatePrintWriter.NULL, params, + new BasicNamespace(template), + new NabbleNamespace(this) + ); + } + } + kill(); + } + + public void kill() { + if( !db().isInTransaction() ) { + db().beginTransaction(); + try { + SiteImpl site = DbUtils.getGoodCopy(SiteImpl.this); + site.kill(); + db().commitTransaction(); + } finally { + db().endTransaction(); + } + return; + } + SiteGlobal siteGlobal = siteGlobal(); + if( siteGlobal == null ) + throw new NullPointerException("siteGlobal not found for "+siteKey); + try { + Connection con = Db.dbPostgres().getConnection(); + Statement stmt = con.createStatement(); + stmt.executeUpdate( + "drop schema " + siteKey.schema() + " cascade" + ); + DbUtils.uncache(this); + stmt.close(); + con.close(); + } catch(SQLException e) { + throw new RuntimeException(e); + } + siteGlobal.getDbRecord().delete(); + } + + private static final String SALT = "zDf3s"; + private static final File schemaDir = new File((String)Init.get("local_dir")+"schemas/"); + + private static File getBackupFile(long siteId) { + int hash = Math.abs(( SALT + siteId ).hashCode()); + String filename = "site_"+siteId+"_"+hash+".zip"; + return new File(schemaDir,filename); + } + + public File backup() { + File file = getBackupFile(getId()); + backup(file); + return file; + } + + public void backup(String filename) { + backup( new File(filename) ); + } + + private void backup(File file) { + file.delete(); + ZipBackup backup = new ZipBackup( file, Db.completeUrl ); + backup.dump( Collections.singleton(siteKey.schema()), DataFilter.ALL_DATA ); + } + + static final DataFilter SCHEMA_DATA = new DataFilter() { + public boolean dumpData(String schema,String tableName) { + return tableName.equals("version"); + } + }; + + public void backupSchema(String filename) { + ZipBackup backup = new ZipBackup( new File(filename), Db.completeUrl ); + backup.dump( Collections.singleton(siteKey.schema()), SCHEMA_DATA ); + } +// in beanshell, I do: +// s = ModelHome.getSite(2) +// s.backupSchema("/Users/Franklin/hg/nabble/src/nabble/data/site.schema") + +}