/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
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.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.core.xml.CastorUtils;
import org.opennms.netmgt.EventConstants;
import org.opennms.netmgt.config.groups.Group;
import org.opennms.netmgt.config.groups.Groupinfo;
import org.opennms.netmgt.config.groups.Groups;
import org.opennms.netmgt.config.groups.Header;
import org.opennms.netmgt.config.groups.Role;
import org.opennms.netmgt.config.groups.Roles;
import org.opennms.netmgt.config.groups.Schedule;
import org.opennms.netmgt.config.users.DutySchedule;
import org.opennms.netmgt.model.OnmsGroup;
import org.opennms.netmgt.model.OnmsGroupList;
/**
* <p>Abstract GroupManager class.</p>
*
* @author <a href="mailto:david@opennms.org">David Hustace</a>
* @author <a href="mailto:brozow@opennms.org">Matt Brozowski</a>
* @author <a href="mailto:ayres@net.orst.edu">Bill Ayres</a>
* @author <a href="mailto:dj@gregor.com">DJ Gregor</a>
*/
public abstract class GroupManager {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* The duty schedules for each group
*/
protected static HashMap<String, List<DutySchedule>> m_dutySchedules;
/**
* A mapping of Group object by name
*/
private Map<String, Group> m_groups;
private Map<String, Role> m_roles;
private Header m_oldHeader;
/**
* <p>parseXml</p>
*
* @param stream a {@link java.io.InputStream} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
protected synchronized void parseXml(InputStream stream) throws MarshalException, ValidationException {
Groupinfo groupinfo = CastorUtils.unmarshal(Groupinfo.class, stream);
initializeGroupsAndRoles(groupinfo);
}
private void initializeGroupsAndRoles(Groupinfo groupinfo) {
Groups groups = groupinfo.getGroups();
m_groups = new LinkedHashMap<String, Group>();
m_oldHeader = groupinfo.getHeader();
for (Group curGroup : groups.getGroupCollection()) {
m_groups.put(curGroup.getName(), curGroup);
}
buildDutySchedules(m_groups);
Roles roles = groupinfo.getRoles();
m_roles = new LinkedHashMap<String, Role>();
if (roles != null) {
for (Role role : roles.getRoleCollection()) {
m_roles.put(role.getName(), role);
}
}
}
/**
* Set the groups data
*
* @param grp a {@link java.util.Map} object.
*/
public void setGroups(Map<String, Group> grp) {
m_groups = grp;
}
/**
* Get the groups
*
* @return a {@link java.util.Map} object.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public Map<String, Group> getGroups() throws IOException, MarshalException, ValidationException {
update();
return Collections.unmodifiableMap(m_groups);
}
public OnmsGroupList getOnmsGroupList() throws MarshalException, ValidationException, IOException {
final OnmsGroupList list = new OnmsGroupList();
for (final String name : getGroupNames()) {
list.add(getOnmsGroup(name));
}
list.setTotalCount(list.getCount());
return list;
}
public OnmsGroup getOnmsGroup(final String groupName) throws MarshalException, ValidationException, IOException {
final Group castorGroup = getGroup(groupName);
if (castorGroup == null) return null;
final OnmsGroup group = new OnmsGroup(groupName);
group.setComments(castorGroup.getComments());
group.setUsers(castorGroup.getUserCollection());
return group;
}
public synchronized void save(final OnmsGroup group) throws Exception {
Group castorGroup = getGroup(group.getName());
if (castorGroup == null) {
castorGroup = new Group();
castorGroup.setName(group.getName());
}
castorGroup.setComments(group.getComments());
castorGroup.setUser(group.getUsers().toArray(EMPTY_STRING_ARRAY));
saveGroup(group.getName(), castorGroup);
}
/**
* <p>update</p>
*
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws java.io.IOException if any.
*/
public abstract void update() throws IOException, MarshalException, ValidationException;
/**
* Returns a boolean indicating if the group name appears in the xml file
*
* @return true if the group exists in the xml file, false otherwise
* @param groupName a {@link java.lang.String} object.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public boolean hasGroup(String groupName) throws IOException, MarshalException, ValidationException {
update();
return m_groups.containsKey(groupName);
}
/**
* <p>getGroupNames</p>
*
* @return a {@link java.util.List} object.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public List<String> getGroupNames() throws IOException, MarshalException, ValidationException {
update();
return new ArrayList<String>(m_groups.keySet());
}
/**
* Get a group using its name
*
* @param name
* the name of the group to return
* @return Group, the group specified by name
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public Group getGroup(String name) throws IOException, MarshalException, ValidationException {
update();
return m_groups.get(name);
}
/**
* <p>saveGroups</p>
*
* @throws java.lang.Exception if any.
*/
public synchronized void saveGroups() throws Exception {
Header header = m_oldHeader;
if (header != null) header.setCreated(EventConstants.formatToString(new Date()));
Groups groups = new Groups();
for (Group grp : m_groups.values()) {
groups.addGroup(grp);
}
Roles roles = new Roles();
for (Role role : m_roles.values()) {
roles.addRole(role);
}
Groupinfo groupinfo = new Groupinfo();
groupinfo.setGroups(groups);
if (roles.getRoleCount() > 0)
groupinfo.setRoles(roles);
groupinfo.setHeader(header);
m_oldHeader = header;
// marshal to a string first, then write the string to the file. This
// way the original configuration
// isn't lost if the XML from the marshal is hosed.
StringWriter stringWriter = new StringWriter();
Marshaller.marshal(groupinfo, stringWriter);
String data = stringWriter.toString();
saveXml(data);
}
/**
* Builds a mapping between groups and duty schedules. These are used when
* determining to send a notice to a given group. This helps speed up the decision process.
* @param groups the map of groups parsed from the XML configuration file
*/
private static void buildDutySchedules(Map<String, Group> groups) {
m_dutySchedules = new HashMap<String, List<DutySchedule>>();
Iterator<String> i = groups.keySet().iterator();
while(i.hasNext()) {
String key = i.next();
Group curGroup = groups.get(key);
if (curGroup.getDutyScheduleCount() > 0) {
List<DutySchedule> dutyList = new ArrayList<DutySchedule>();
for (String duty : curGroup.getDutyScheduleCollection()) {
dutyList.add(new DutySchedule(duty));
}
m_dutySchedules.put(key, dutyList);
}
}
}
/**
* Determines if a group is on duty at a given time. If a group has no duty schedules
* listed in the configuration file, that group is assumed to always be on duty.
*
* @param group the group whose duty schedule we want
* @param time the time to check for a duty schedule
* @return boolean, true if the group is on duty, false otherwise.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public boolean isGroupOnDuty(String group, Calendar time) throws IOException, MarshalException, ValidationException {
update();
//if the group has no duty schedules then it is on duty
if (!m_dutySchedules.containsKey(group)) {
return true;
}
List<DutySchedule> dutySchedules = m_dutySchedules.get(group);
for (DutySchedule curSchedule : dutySchedules) {
if (curSchedule.isInSchedule(time)) {
return true;
}
}
return false;
}
/**
* Determines when a group is next on duty. If a group has no duty schedules
* listed in the configuration file, that group is assumed to always be on duty.
*
* @param group the group whose duty schedule we want
* @param time the time to check for a duty schedule
* @return long, the time in milliseconds until the group is next on duty
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public long groupNextOnDuty(String group, Calendar time) throws IOException, MarshalException, ValidationException {
ThreadCategory log = ThreadCategory.getInstance(this.getClass());
long next = -1;
update();
//if the group has no duty schedules then it is on duty
if (!m_dutySchedules.containsKey(group)) {
return 0;
}
List<DutySchedule> dutySchedules = m_dutySchedules.get(group);
for (int i = 0; i < dutySchedules.size(); i++) {
DutySchedule curSchedule = dutySchedules.get(i);
long tempnext = curSchedule.nextInSchedule(time);
if( tempnext < next || next == -1 ) {
if (log.isDebugEnabled()) {
log.debug("isGroupOnDuty: On duty in " + tempnext + " millisec from schedule " + i);
}
next = tempnext;
}
}
return next;
}
/**
* <p>saveXml</p>
*
* @param data a {@link java.lang.String} object.
* @throws java.io.IOException if any.
*/
protected abstract void saveXml(String data) throws IOException;
/**
* Adds a new user and overwrites the "groups.xml"
*
* @param name a {@link java.lang.String} object.
* @param details a {@link org.opennms.netmgt.config.groups.Group} object.
* @throws java.lang.Exception if any.
*/
public synchronized void saveGroup(String name, Group details) throws Exception {
if (name == null || details == null) {
throw new Exception("GroupFactory:saveGroup null");
} else {
m_groups.put(name, details);
}
saveGroups();
}
/**
* <p>saveRole</p>
*
* @param role a {@link org.opennms.netmgt.config.groups.Role} object.
* @throws java.lang.Exception if any.
*/
public void saveRole(Role role) throws Exception {
m_roles.put(role.getName(), role);
saveGroups();
}
/**
* Removes the user from the list of groups. Then overwrites to the
* "groups.xml"
*
* @param name a {@link java.lang.String} object.
* @throws java.lang.Exception if any.
*/
public synchronized void deleteUser(String name) throws Exception {
// Check if the user exists
if (name != null && !name.equals("")) {
// Remove the user in the group.
for (Group group : m_groups.values()) {
group.removeUser(name);
}
for (Role role : m_roles.values()) {
Iterator<Schedule> s = role.getScheduleCollection().iterator();
while(s.hasNext()) {
Schedule sched = s.next();
if (name.equals(sched.getName())) {
s.remove();
}
}
}
} else {
throw new Exception("GroupFactory:delete Invalid user name:" + name);
}
// Saves into "groups.xml" file
saveGroups();
}
/**
* Removes the group from the list of groups. Then overwrites to the
* "groups.xml"
*
* @param name a {@link java.lang.String} object.
* @throws java.lang.Exception if any.
*/
public synchronized void deleteGroup(String name) throws Exception {
// Check if the group exists
if (name != null && !name.equals("")) {
if (m_groups.containsKey(name)) {
// Remove the group.
m_groups.remove(name);
} else
throw new Exception("GroupFactory:delete Group doesnt exist:" + name);
} else {
throw new Exception("GroupFactory:delete Invalid user group:" + name);
}
// Saves into "groups.xml" file
saveGroups();
}
/**
* <p>deleteRole</p>
*
* @param name a {@link java.lang.String} object.
* @throws java.lang.Exception if any.
*/
public void deleteRole(String name) throws Exception {
if (name != null && !name.equals("")) {
if (m_roles.containsKey(name)) {
m_roles.remove(name);
}
else
throw new Exception("GroupFacotry:deleteRole Role doesn't exist: "+name);
}
else
throw new Exception("GroupFactory:deleteRole Invalid role name: "+name);
saveGroups();
}
/**
* Renames the group from the list of groups. Then overwrites to the
* "groups.xml"
*
* @param oldName a {@link java.lang.String} object.
* @param newName a {@link java.lang.String} object.
* @throws java.lang.Exception if any.
*/
public synchronized void renameGroup(String oldName, String newName) throws Exception {
if (oldName != null && !oldName.equals("")) {
if (m_groups.containsKey(oldName)) {
Group grp = m_groups.remove(oldName);
grp.setName(newName);
m_groups.put(newName, grp);
} else {
throw new Exception("GroupFactory.renameGroup: Group doesn't exist: " + oldName);
}
// Save into groups.xml
saveGroups();
}
}
/**
* When this method is called group name is changed, so also is the
* group name belonging to the view. Also overwrites the "groups.xml" file
*
* @param oldName a {@link java.lang.String} object.
* @param newName a {@link java.lang.String} object.
* @throws java.lang.Exception if any.
*/
public synchronized void renameUser(String oldName, String newName) throws Exception {
// Get the old data
if (oldName == null || newName == null || oldName == "" || newName == "") {
throw new Exception("Group Factory: Rename user.. no value ");
} else {
Map<String, Group> map = new LinkedHashMap<String, Group>();
for (Group group : m_groups.values()) {
for(ListIterator<String> userList = group.getUserCollection().listIterator(); userList.hasNext();){
String name = userList.next();
if(name.equals(oldName)){
userList.set(newName);
}
}
map.put(group.getName(), group);
}
m_groups.clear();
m_groups.putAll(map);
for (Role role : m_roles.values()) {
for (Schedule sched : role.getScheduleCollection()) {
if (oldName.equals(sched.getName())) {
sched.setName(newName);
}
}
}
saveGroups();
}
}
/**
* <p>getRoleNames</p>
*
* @return an array of {@link java.lang.String} objects.
*/
public String[] getRoleNames() {
return (String[]) m_roles.keySet().toArray(new String[m_roles.keySet().size()]);
}
/**
* <p>getRoles</p>
*
* @return a {@link java.util.Collection} object.
*/
public Collection<Role> getRoles() {
return m_roles.values();
}
/**
* <p>getRole</p>
*
* @param roleName a {@link java.lang.String} object.
* @return a {@link org.opennms.netmgt.config.groups.Role} object.
*/
public Role getRole(String roleName) {
return (Role)m_roles.get(roleName);
}
/**
* <p>userHasRole</p>
*
* @param userId a {@link java.lang.String} object.
* @param roleid a {@link java.lang.String} object.
* @return a boolean.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
public boolean userHasRole(String userId, String roleid) throws MarshalException, ValidationException, IOException {
update();
for (Schedule sched : getRole(roleid).getScheduleCollection()) {
if (userId.equals(sched.getName())) {
return true;
}
}
return false;
}
/**
* <p>getSchedulesForRoleAt</p>
*
* @param roleId a {@link java.lang.String} object.
* @param time a {@link java.util.Date} object.
* @return a {@link java.util.List} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
public List<Schedule> getSchedulesForRoleAt(String roleId, Date time) throws MarshalException, ValidationException, IOException {
update();
List<Schedule> schedules = new ArrayList<Schedule>();
for (Schedule sched : getRole(roleId).getScheduleCollection()) {
if (BasicScheduleUtils.isTimeInSchedule(time, BasicScheduleUtils.getGroupSchedule(sched))) {
schedules.add(sched);
}
}
return schedules;
}
/**
* <p>getUserSchedulesForRole</p>
*
* @param userId a {@link java.lang.String} object.
* @param roleId a {@link java.lang.String} object.
* @return a {@link java.util.List} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
public List<Schedule> getUserSchedulesForRole(String userId, String roleId) throws MarshalException, ValidationException, IOException {
update();
List<Schedule> scheds = new ArrayList<Schedule>();
for (Schedule sched : getRole(roleId).getScheduleCollection()) {
if (userId.equals(sched.getName())) {
scheds.add(sched);
}
}
return scheds;
}
/**
* <p>isUserScheduledForRole</p>
*
* @param userId a {@link java.lang.String} object.
* @param roleId a {@link java.lang.String} object.
* @param time a {@link java.util.Date} object.
* @return a boolean.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
public boolean isUserScheduledForRole(String userId, String roleId, Date time) throws MarshalException, ValidationException, IOException {
update();
for (Schedule sched : getUserSchedulesForRole(userId, roleId)) {
if (BasicScheduleUtils.isTimeInSchedule(time, BasicScheduleUtils.getGroupSchedule(sched))) {
return true;
}
}
// if no user is scheduled then the supervisor is schedule by default
Role role = getRole(roleId);
if (userId.equals(role.getSupervisor())) {
for (Schedule sched : role.getScheduleCollection()) {
if (BasicScheduleUtils.isTimeInSchedule(time, BasicScheduleUtils.getGroupSchedule(sched))) {
// we found another scheduled user
return false;
}
}
return true;
}
return false;
}
/**
* <p>getRoleScheduleEntries</p>
*
* @param roleid a {@link java.lang.String} object.
* @param start a {@link java.util.Date} object.
* @param end a {@link java.util.Date} object.
* @return a {@link org.opennms.netmgt.config.OwnedIntervalSequence} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
public OwnedIntervalSequence getRoleScheduleEntries(String roleid, Date start, Date end) throws MarshalException, ValidationException, IOException {
update();
OwnedIntervalSequence schedEntries = new OwnedIntervalSequence();
Role role = getRole(roleid);
for (int i = 0; i < role.getScheduleCount(); i++) {
Schedule sched = (Schedule) role.getSchedule(i);
Owner owner = new Owner(roleid, sched.getName(), i);
schedEntries.addAll(BasicScheduleUtils.getIntervalsCovering(start, end, BasicScheduleUtils.getGroupSchedule(sched), owner));
}
OwnedIntervalSequence defaultEntries = new OwnedIntervalSequence(new OwnedInterval(start, end));
defaultEntries.removeAll(schedEntries);
Owner supervisor = new Owner(roleid, role.getSupervisor());
for (Iterator<OwnedInterval> it = defaultEntries.iterator(); it.hasNext();) {
OwnedInterval interval = it.next();
interval.addOwner(supervisor);
}
schedEntries.addAll(defaultEntries);
return schedEntries;
}
/**
* <p>findGroupsForUser</p>
*
* @param user a {@link java.lang.String} object.
* @return a {@link java.util.List} object.
*/
public List<Group> findGroupsForUser(String user) {
List<Group> groups = new ArrayList<Group>();
for (Group group : m_groups.values()) {
if (group.getUserCollection().contains(user)) {
groups.add(group);
}
}
return groups;
}
}