package com.sissi.ucenter.relation.roster.impl; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.WriteConcern; import com.sissi.config.Dictionary; import com.sissi.config.MongoConfig; import com.sissi.config.impl.MongoUtils; import com.sissi.context.JID; import com.sissi.context.JIDBuilder; import com.sissi.protocol.iq.roster.RosterSubscription; import com.sissi.ucenter.relation.Relation; import com.sissi.ucenter.relation.RelationContext; import com.sissi.ucenter.relation.roster.RelationAck; import com.sissi.ucenter.relation.roster.RelationCascade; /** * 索引策略1: {"master":1,"slave":1}</p> 索引策略2: {"slave":1,"master":1}</p> 索引策略3: {"master":1,"activate":1}</p> 索引策略4: {"slave":1,"activate":1}</p> 索引策略5: {"master":1,"activate":1,"status":1}</p> 索引策略6: {"slave":1,"activate":1,"status":1}</p> 索引策略7: {"slave":1,"ack":1} * @author kim 2014年4月22日 */ public class MongoRelationRosterContext implements RelationContext, RelationAck { private final Log log = LogFactory.getLog(this.getClass()); private final Map<String, RelationUpdate> update = new HashMap<String, RelationUpdate>(); private final Map<String, Object> plus = Collections.unmodifiableMap(new HashMap<String, Object>()); /** * {"slave":1} */ private final DBObject filterSlave = BasicDBObjectBuilder.start(Dictionary.FIELD_SLAVE, 1).get(); /** * {"master":1} */ private final DBObject filterMaster = BasicDBObjectBuilder.start(Dictionary.FIELD_MASTER, 1).get(); /** * 初始化Master,{"status":0} */ private final DBObject initMaster = BasicDBObjectBuilder.start(Dictionary.FIELD_STATUS, 0).get(); /** * 初始化Salve,{"activate":false,"status":0} */ private final DBObject initSlave = BasicDBObjectBuilder.start().add(Dictionary.FIELD_ACTIVATE, false).add(Dictionary.FIELD_STATUS, 0).get(); /** * 建立订阅To,{"or":1} */ private final DBObject establishTo = BasicDBObjectBuilder.start("or", 1).get(); /** * 建立订阅From,{"or":2} */ private final DBObject establishFrom = BasicDBObjectBuilder.start("or", 2).get(); /** * 删除订阅To,{"and":1} */ private final DBObject brokeTo = BasicDBObjectBuilder.start("and", 1).get(); /** * 删除订阅From,{"and":1} */ private final DBObject brokeFrom = BasicDBObjectBuilder.start("and", 2).get(); /** * 有效的订阅关系,[{"status":1},{"status":3}] */ private final DBObject[] states = new DBObject[] { BasicDBObjectBuilder.start(Dictionary.FIELD_STATUS, 1).get(), BasicDBObjectBuilder.start(Dictionary.FIELD_STATUS, 3).get() }; /** * 默认Group */ private final String[] groups; private final MongoConfig config; private final JIDBuilder jidBuilder; private final MongoOurRelation ourRelation; private final RelationCascade relationCascade; public MongoRelationRosterContext(String group, MongoConfig config, JIDBuilder jidBuilder, MongoOurRelation ourRelation, RelationCascade relationCascade) { super(); this.config = config; this.jidBuilder = jidBuilder; this.ourRelation = ourRelation; this.relationCascade = relationCascade; this.groups = new String[] { group }; this.update.put(RosterSubscription.NONE.toString(), new RelationUpdate(this.brokeTo, this.brokeFrom)); this.update.put(RosterSubscription.TO.toString(), new RelationUpdate(this.establishFrom, this.establishTo)); } /** * {"master":1,"slave":1} * * @param master * @param slave * @return */ private DBObject buildQuery(String master, String slave) { return this.ourRelation.buildQuery(master, slave); } /** * {角色:jid,"activate":true} * * @param role * @param jid * @return */ private DBObject buildQueryWithRole(String role, String jid) { return BasicDBObjectBuilder.start().add(role, jid).add(Dictionary.FIELD_ACTIVATE, true).get(); } /** * {角色:jid,"activate":true,"$or":有效的订阅关系,[{"status":1},{"status":3}]} * * @param role * @param jid * @param status * @return */ private DBObject buildQueryWithStates(String role, String jid, DBObject[] status) { DBObject query = this.buildQueryWithRole(role, jid); query.put("$or", status); return query; } @Override public MongoRelationRosterContext establish(JID from, Relation relation) { // {"$set":{(...relation.plus()...),"nick":relation.name(),"activate":true},"$setOnInsert":{"status":0}} // {"$setOnInsert":{"activate":false,"status":0}} if (MongoUtils.success(this.config.collection().update(this.buildQuery(from.asStringWithBare(), relation.jid()), BasicDBObjectBuilder.start().add("$set", BasicDBObjectBuilder.start(relation.plus()).add(Dictionary.FIELD_NICK, relation.name()).add(Dictionary.FIELD_ACTIVATE, true).get()).add("$setOnInsert", this.initMaster).get(), true, false, WriteConcern.SAFE)) && MongoUtils.success(this.config.collection().update(this.buildQuery(relation.jid(), from.asStringWithBare()), BasicDBObjectBuilder.start("$setOnInsert", this.initSlave).get(), true, false, WriteConcern.SAFE))) { return this; } this.log.error("Establish warning: " + from.asStringWithBare() + " / " + relation.jid()); return this; } @Override public MongoRelationRosterContext update(JID from, JID to, String state) { // {"$unset":{"ack":true},"$bit":{"status",this.update.Xxx}} // {"$bit":{"status",this.update.Xxx}} if (MongoUtils.success(this.config.collection().update(this.buildQuery(from.asStringWithBare(), to.asStringWithBare()), BasicDBObjectBuilder.start().add("$unset", BasicDBObjectBuilder.start(Dictionary.FIELD_ACK, true).get()).add("$bit", BasicDBObjectBuilder.start().add(Dictionary.FIELD_STATUS, this.update.get(state).getTo()).get()).get(), true, false, WriteConcern.SAFE)) && MongoUtils.success(this.config.collection().update(this.buildQuery(to.asStringWithBare(), from.asStringWithBare()), BasicDBObjectBuilder.start("$bit", BasicDBObjectBuilder.start().add(Dictionary.FIELD_STATUS, this.update.get(state).getFrom()).get()).get(), true, false, WriteConcern.SAFE))) { this.relationCascade.update(to, from); return this; } this.log.error("Update warning: " + from.asStringWithBare() + " / " + to.asStringWithBare() + " / " + state); return this; } public MongoRelationRosterContext remove(JID from, JID to) { if (MongoUtils.success(this.config.collection().remove(this.buildQuery(from.asStringWithBare(), to.asStringWithBare()), WriteConcern.SAFE)) && MongoUtils.success(this.config.collection().remove(this.buildQuery(to.asStringWithBare(), from.asStringWithBare()), WriteConcern.SAFE))) { this.relationCascade.remove(to, from); } else { this.log.error("Remove warning: " + from.asStringWithBare() + " / " + to.asStringWithBare()); } return this; } /* * {"master":jid.bare,"activate":true} * * @see com.sissi.ucenter.relation.RelationContext#myRelations(com.sissi.context.JID) */ @Override public Set<Relation> myRelations(JID from) { return new MongoRelations(this.config.collection().find(this.buildQueryWithRole(Dictionary.FIELD_MASTER, from.asStringWithBare()))); } @Override public Relation ourRelation(JID from, JID to) { return this.ourRelation.ourRelation(from, to); } @Override public Set<JID> whoSubscribedMe(JID from) { return new MongoJIDGroup(this.config.collection().find(this.buildQueryWithStates(Dictionary.FIELD_SLAVE, from.asStringWithBare(), this.states), this.filterMaster), Dictionary.FIELD_MASTER); } public Set<JID> iSubscribedWho(JID from) { return new MongoJIDGroup(this.config.collection().find(this.buildQueryWithStates(Dictionary.FIELD_MASTER, from.asStringWithBare(), this.states), this.filterSlave), Dictionary.FIELD_SLAVE); } /* * {"slave":jid.bare,"ack":true} * * @see com.sissi.ucenter.relation.roster.RelationAck#ack(com.sissi.context.JID) */ @Override public Set<Relation> ack(JID jid) { return new MongoRelations(this.config.collection().find(BasicDBObjectBuilder.start().add(Dictionary.FIELD_SLAVE, jid.asStringWithBare()).add(Dictionary.FIELD_ACK, true).get()), Dictionary.FIELD_MASTER); } private class RelationUpdate { private final DBObject from; private final DBObject to; public RelationUpdate(DBObject from, DBObject to) { super(); this.from = from; this.to = to; } public DBObject getFrom() { return this.from; } public DBObject getTo() { return this.to; } } private class MongoRelations extends HashSet<Relation> { private final static long serialVersionUID = 1L; private MongoRelations(DBCursor cursor) { this(cursor, Dictionary.FIELD_SLAVE); } /** * @param cursor * @param field */ private MongoRelations(DBCursor cursor, String field) { try (DBCursor iterator = cursor) { if (iterator == null) { return; } while (iterator.hasNext()) { this.add(new MongoRosterRelation(iterator.next(), field, MongoRelationRosterContext.this.groups, MongoRelationRosterContext.this.plus)); } } } } private class MongoJIDGroup extends HashSet<JID> { private final static long serialVersionUID = 1L; private MongoJIDGroup(DBCursor cursor, String key) { try (DBCursor iterator = cursor) { if (iterator == null) { return; } while (iterator.hasNext()) { this.add(MongoRelationRosterContext.this.jidBuilder.build(iterator.next().get(key).toString())); } } } } }