diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/BranchesResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/BranchesResource.java new file mode 100644 index 0000000000..be9f61bbd7 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/BranchesResource.java @@ -0,0 +1,519 @@ +/* + * + * BranchesResource.java - A Restlet to read Branch data from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.branch.Branch; +import org.sipfoundry.sipxconfig.branch.BranchManager; +import org.sipfoundry.sipxconfig.phonebook.Address; +import org.sipfoundry.sipxconfig.rest.RestUtilities.BranchRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class BranchesResource extends UserResource { + + private BranchManager m_branchManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, CITY, OFFICEDESIGNATION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Branch + // ------------------------------------ + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + BranchRestInfoFull branchRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + branchRestInfo = createBranchRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Skills failed", exception.getLocalizedMessage()); + } + + return new BranchRepresentation(variant.getMediaType(), branchRestInfo); + } + + + // if not single, process request for all + List branches = m_branchManager.getBranches(); + List branchesRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortBranches(branches); + + // set requested agents groups and get resulting metadata + metadataRestInfo = addBranches(branchesRestInfo, branches); + + // create final restinfo + BranchesBundleRestInfo branchesBundleRestInfo = new BranchesBundleRestInfo(branchesRestInfo, metadataRestInfo); + + return new BranchesRepresentation(variant.getMediaType(), branchesBundleRestInfo); + } + + + // PUT - Update or Add single Branch + // --------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + BranchRepresentation representation = new BranchRepresentation(entity); + BranchRestInfoFull branchRestInfo = representation.getObject(); + Branch branch = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(branchRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + branch = m_branchManager.getBranch(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateBranch(branch, branchRestInfo); + m_branchManager.saveBranch(branch); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Branch failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Branch", branch.getId()); + + return; + } + + + // otherwise add new + try { + branch = createBranch(branchRestInfo); + m_branchManager.saveBranch(branch); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Branch failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Branch", branch.getId()); + } + + + // DELETE - Delete single Branch + // ----------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + Branch branch; + int idInt; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + branch = m_branchManager.getBranch(idInt); // just obtain to make sure exists, use int id for actual delete + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + List branchIds = new ArrayList(); + branchIds.add(idInt); + m_branchManager.deleteBranches(branchIds); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Branch", branch.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(BranchRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private BranchRestInfoFull createBranchRestInfo(int id) throws ResourceException { + BranchRestInfoFull branchRestInfo = null; + + Branch branch = m_branchManager.getBranch(id); + branchRestInfo = new BranchRestInfoFull(branch); + + return branchRestInfo; + } + + private MetadataRestInfo addBranches(List branchesRestInfo, List branches) { + BranchRestInfoFull branchRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, branches.size()); + + // create list of skill restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + Branch branch = branches.get(index); + + branchRestInfo = new BranchRestInfoFull(branch); + branchesRestInfo.add(branchRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortBranches(List branches) { + // sort if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case CITY: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch1.getAddress().getCity(), branch2.getAddress().getCity()); + } + + }); + break; + + case OFFICEDESIGNATION: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch1.getAddress().getOfficeDesignation(), branch2.getAddress().getOfficeDesignation()); + } + + }); + break; + + case NAME: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch1.getName(),branch2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch1.getDescription(),branch2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case CITY: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch2.getAddress().getCity(), branch1.getAddress().getCity()); + } + + }); + break; + + case OFFICEDESIGNATION: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch2.getAddress().getOfficeDesignation(), branch1.getAddress().getOfficeDesignation()); + } + + }); + break; + + case NAME: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch2.getName(),branch1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(branches, new Comparator() { + + public int compare(Object object1, Object object2) { + Branch branch1 = (Branch) object1; + Branch branch2 = (Branch) object2; + return RestUtilities.compareIgnoreCaseNullSafe(branch2.getDescription(),branch1.getDescription()); + } + + }); + break; + } + } + } + + private void updateBranch(Branch branch, BranchRestInfoFull branchRestInfo) { + Address address; + String tempString; + + // do not allow empty name + tempString = branchRestInfo.getName(); + if (!tempString.isEmpty()) { + branch.setName(tempString); + } + + branch.setDescription(branchRestInfo.getDescription()); + branch.setPhoneNumber(branchRestInfo.getPhoneNumber()); + branch.setFaxNumber(branchRestInfo.getFaxNumber()); + + address = getAddress(branchRestInfo); + branch.setAddress(address); + } + + private Branch createBranch(BranchRestInfoFull branchRestInfo) throws ResourceException { + Address address; + Branch branch = new Branch(); + + // copy fields from rest info + branch.setName(branchRestInfo.getName()); + branch.setDescription(branchRestInfo.getDescription()); + branch.setPhoneNumber(branchRestInfo.getPhoneNumber()); + branch.setFaxNumber(branchRestInfo.getFaxNumber()); + + address = getAddress(branchRestInfo); + branch.setAddress(address); + + return branch; + } + + private Address getAddress(BranchRestInfoFull branchRestInfo) { + Address address = new Address(); + + address.setCity(branchRestInfo.getAddress().getCity()); + address.setCountry(branchRestInfo.getAddress().getCountry()); + address.setOfficeDesignation(branchRestInfo.getAddress().getOfficeDesignation()); + address.setState(branchRestInfo.getAddress().getState()); + address.setStreet(branchRestInfo.getAddress().getStreet()); + address.setZip(branchRestInfo.getAddress().getZip()); + + return address; + } + + + // REST Representations + // -------------------- + + static class BranchesRepresentation extends XStreamRepresentation { + + public BranchesRepresentation(MediaType mediaType, BranchesBundleRestInfo object) { + super(mediaType, object); + } + + public BranchesRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("branch", BranchesBundleRestInfo.class); + xstream.alias("branch", BranchRestInfoFull.class); + } + } + + static class BranchRepresentation extends XStreamRepresentation { + + public BranchRepresentation(MediaType mediaType, BranchRestInfoFull object) { + super(mediaType, object); + } + + public BranchRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("branch", BranchRestInfoFull.class); + } + } + + + // REST info objects + // ----------------- + + static class BranchesBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_branches; + + public BranchesBundleRestInfo(List branches, MetadataRestInfo metadata) { + m_metadata = metadata; + m_branches = branches; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getBranches() { + return m_branches; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setBranchManager(BranchManager branchManager) { + m_branchManager = branchManager; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentGroupsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentGroupsResource.java new file mode 100644 index 0000000000..ed580afc8d --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentGroupsResource.java @@ -0,0 +1,566 @@ +/* + * + * OpenAcdAgentGroupsResource.java - A Restlet to read group data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgentGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdClient; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueue; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdAgentGroupRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdClientRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdQueueRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdAgentGroupsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve Groups and single Group + // -------------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdAgentGroupRestInfoFull agentGroupRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + agentGroupRestInfo = createAgentGroupRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Agent Group failed", exception.getLocalizedMessage()); + } + + return new OpenAcdAgentGroupRepresentation(variant.getMediaType(), agentGroupRestInfo); + } + + + // if not single, process request for all + List agentGroups = m_openAcdContext.getAgentGroups(); + List agentGroupsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortGroups(agentGroups); + + // set requested based on pagination and get resulting metadata + metadataRestInfo = addAgentGroups(agentGroupsRestInfo, agentGroups); + + // create final restinfo + OpenAcdAgentGroupsBundleRestInfo agentGroupsBundleRestInfo = new OpenAcdAgentGroupsBundleRestInfo(agentGroupsRestInfo, metadataRestInfo); + + return new OpenAcdAgentGroupsRepresentation(variant.getMediaType(), agentGroupsBundleRestInfo); + } + + + // PUT - Update or Add single Group + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get group from body + OpenAcdAgentGroupRepresentation representation = new OpenAcdAgentGroupRepresentation(entity); + OpenAcdAgentGroupRestInfoFull agentGroupRestInfo = representation.getObject(); + OpenAcdAgentGroup agentGroup; + + // validate input for update or create + ValidationInfo validationInfo = validate(agentGroupRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update a single group + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + agentGroup = m_openAcdContext.getAgentGroupById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing group + try { + updateAgentGroup(agentGroup, agentGroupRestInfo); + m_openAcdContext.saveAgentGroup(agentGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Agent Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Agent Group", agentGroup.getId()); + + return; + } + + + // otherwise add new agent group + try { + agentGroup = createAgentGroup(agentGroupRestInfo); + m_openAcdContext.saveAgentGroup(agentGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Agent Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Agent Group", agentGroup.getId()); + } + + + // DELETE - Delete single Group + // -------------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdAgentGroup agentGroup; + + // get id then delete a single group + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + agentGroup = m_openAcdContext.getAgentGroupById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteAgentGroup(agentGroup); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Agent Group", agentGroup.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdAgentGroupRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + String name = restInfo.getName(); + + for (int i = 0; i < name.length(); i++) { + if ((!Character.isLetterOrDigit(name.charAt(i)) && !(Character.getType(name.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && name.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Name' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + return validationInfo; + } + + private OpenAcdAgentGroupRestInfoFull createAgentGroupRestInfo(int id) throws ResourceException { + OpenAcdAgentGroupRestInfoFull agentGroupRestInfo; + List skillsRestInfo; + List queuesRestInfo; + List clientsRestInfo; + + OpenAcdAgentGroup agentGroup = m_openAcdContext.getAgentGroupById(id); + + skillsRestInfo = createSkillsRestInfo(agentGroup); + queuesRestInfo = createQueuesRestInfo(agentGroup); + clientsRestInfo = createClientsRestInfo(agentGroup); + agentGroupRestInfo = new OpenAcdAgentGroupRestInfoFull(agentGroup, skillsRestInfo, queuesRestInfo, clientsRestInfo); + + return agentGroupRestInfo; + } + + private MetadataRestInfo addAgentGroups(List agentGroupsRestInfo, List agentGroups) { + List skillsRestInfo; + List queuesRestInfo; + List clientsRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, agentGroups.size()); + + // create list of group restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdAgentGroup agentGroup = agentGroups.get(index); + skillsRestInfo = createSkillsRestInfo(agentGroup); + queuesRestInfo = createQueuesRestInfo(agentGroup); + clientsRestInfo = createClientsRestInfo(agentGroup); + OpenAcdAgentGroupRestInfoFull agentGroupRestInfo = new OpenAcdAgentGroupRestInfoFull(agentGroup, skillsRestInfo, queuesRestInfo, clientsRestInfo); + agentGroupsRestInfo.add(agentGroupRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private List createSkillsRestInfo(OpenAcdAgentGroup agentGroup) { + List skillsRestInfo; + OpenAcdSkillRestInfo skillRestInfo; + + // create list of skill restinfos for single group + Set groupSkills = agentGroup.getSkills(); + skillsRestInfo = new ArrayList(groupSkills.size()); + + for (OpenAcdSkill groupSkill : groupSkills) { + skillRestInfo = new OpenAcdSkillRestInfo(groupSkill); + skillsRestInfo.add(skillRestInfo); + } + + return skillsRestInfo; + } + + private List createQueuesRestInfo(OpenAcdAgentGroup agentGroup) { + List queuesRestInfo; + OpenAcdQueueRestInfo queueRestInfo; + + // create list of queue restinfos for single group + Set groupQueues = agentGroup.getQueues(); + queuesRestInfo = new ArrayList(groupQueues.size()); + + for (OpenAcdQueue groupQueue : groupQueues) { + queueRestInfo = new OpenAcdQueueRestInfo(groupQueue); + queuesRestInfo.add(queueRestInfo); + } + + return queuesRestInfo; + } + + + private List createClientsRestInfo(OpenAcdAgentGroup agentGroup) { + List clientsRestInfo; + OpenAcdClientRestInfo clientRestInfo; + + // create list of client restinfos for single group + Set groupClients = agentGroup.getClients(); + clientsRestInfo = new ArrayList(groupClients.size()); + + for (OpenAcdClient groupClient : groupClients) { + clientRestInfo = new OpenAcdClientRestInfo(groupClient); + clientsRestInfo.add(clientRestInfo); + } + + return clientsRestInfo; + } + + private void sortGroups(List agentGroups) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(agentGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgentGroup agentGroup1 = (OpenAcdAgentGroup) object1; + OpenAcdAgentGroup agentGroup2 = (OpenAcdAgentGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agentGroup1.getName(), agentGroup2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(agentGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgentGroup agentGroup1 = (OpenAcdAgentGroup) object1; + OpenAcdAgentGroup agentGroup2 = (OpenAcdAgentGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agentGroup1.getDescription(),agentGroup2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(agentGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgentGroup agentGroup1 = (OpenAcdAgentGroup) object1; + OpenAcdAgentGroup agentGroup2 = (OpenAcdAgentGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agentGroup2.getName(), agentGroup1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(agentGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgentGroup agentGroup1 = (OpenAcdAgentGroup) object1; + OpenAcdAgentGroup agentGroup2 = (OpenAcdAgentGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agentGroup2.getDescription(),agentGroup1.getDescription()); + } + + }); + break; + } + } + } + + private void updateAgentGroup(OpenAcdAgentGroup agentGroup, OpenAcdAgentGroupRestInfoFull agentGroupRestInfo) { + String tempString; + + // do not allow empty name + tempString = agentGroupRestInfo.getName(); + if (!tempString.isEmpty()) { + agentGroup.setName(tempString); + } + + agentGroup.setDescription(agentGroupRestInfo.getDescription()); + + addLists(agentGroup, agentGroupRestInfo); + } + + private OpenAcdAgentGroup createAgentGroup(OpenAcdAgentGroupRestInfoFull agentGroupRestInfo) { + OpenAcdAgentGroup agentGroup = new OpenAcdAgentGroup(); + + // copy fields from rest info + agentGroup.setName(agentGroupRestInfo.getName()); + agentGroup.setDescription(agentGroupRestInfo.getDescription()); + + addLists(agentGroup, agentGroupRestInfo); + + return agentGroup; + } + + private void addLists(OpenAcdAgentGroup agentGroup, OpenAcdAgentGroupRestInfoFull agentGroupRestInfo) { + // remove all current skills + agentGroup.getSkills().clear(); + + // set skills + OpenAcdSkill skill; + List skillsRestInfo = agentGroupRestInfo.getSkills(); + for (OpenAcdSkillRestInfo skillRestInfo : skillsRestInfo) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + agentGroup.addSkill(skill); + } + + // remove all current queues + agentGroup.getQueues().clear(); + + // set queues + OpenAcdQueue queue; + List queuesRestInfo = agentGroupRestInfo.getQueues(); + for (OpenAcdQueueRestInfo queueRestInfo : queuesRestInfo) { + queue = m_openAcdContext.getQueueById(queueRestInfo.getId()); + agentGroup.addQueue(queue); + } + + // remove all current clients + agentGroup.getClients().clear(); + + // set clients + OpenAcdClient client; + List clientsRestInfo = agentGroupRestInfo.getClients(); + for (OpenAcdClientRestInfo clientRestInfo : clientsRestInfo) { + client = m_openAcdContext.getClientById(clientRestInfo.getId()); + agentGroup.addClient(client); + } + } + + + // REST Representations + // -------------------- + + static class OpenAcdAgentGroupsRepresentation extends XStreamRepresentation { + + public OpenAcdAgentGroupsRepresentation(MediaType mediaType, OpenAcdAgentGroupsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdAgentGroupsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-agent-group", OpenAcdAgentGroupsBundleRestInfo.class); + xstream.alias("group", OpenAcdAgentGroupRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("queue", OpenAcdQueueRestInfo.class); + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + static class OpenAcdAgentGroupRepresentation extends XStreamRepresentation { + + public OpenAcdAgentGroupRepresentation(MediaType mediaType, OpenAcdAgentGroupRestInfoFull object) { + super(mediaType, object); + } + + public OpenAcdAgentGroupRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", OpenAcdAgentGroupRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("queue", OpenAcdQueueRestInfo.class); + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdAgentGroupsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_groups; + + public OpenAcdAgentGroupsBundleRestInfo(List agentGroups, MetadataRestInfo metadata) { + m_metadata = metadata; + m_groups = agentGroups; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getGroups() { + return m_groups; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentsResource.java new file mode 100644 index 0000000000..3e1f5b0ffe --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdAgentsResource.java @@ -0,0 +1,750 @@ +/* + * + * OpenAcdAgentGroupsResource.java - A Restlet to read Agent data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Waseem + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.common.CoreContext; +import org.sipfoundry.sipxconfig.common.User; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgent; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgentGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdClient; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueue; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdAgentGroupRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdAgentRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdClientRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdQueueRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + + +public class OpenAcdAgentsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private CoreContext m_coreContext; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, GROUP, SECURITY, USERNAME, FIRSTNAME, LASTNAME, BRANCH, EXTENSION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + m_coreContext = getCoreContext(); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Agent + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdAgentRestInfoFull agentRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + agentRestInfo = createAgentRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + agentRestInfo = createAgentRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Agent failed", exception.getLocalizedMessage()); + } + + return new OpenAcdAgentRepresentation(variant.getMediaType(), agentRestInfo); + } + + + // if not single, process request for all + List agents = m_openAcdContext.getAgents(); + List agentsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortAgents(agents); + + // set requested agents groups and get resulting metadata + metadataRestInfo = addAgents(agentsRestInfo, agents); + + // create final restinfo + OpenAcdAgentsBundleRestInfo agentsBundleRestInfo = new OpenAcdAgentsBundleRestInfo(agentsRestInfo, metadataRestInfo); + + return new OpenAcdAgentsRepresentation(variant.getMediaType(), agentsBundleRestInfo); + } + + + // PUT - Update or Add single Agent + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdAgentRepresentation representation = new OpenAcdAgentRepresentation(entity); + OpenAcdAgentRestInfoFull agentRestInfo = (OpenAcdAgentRestInfoFull) representation.getObject(); + OpenAcdAgent agent; + + // validate input for update or create + ValidationInfo validationInfo = validate(agentRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + agent = m_openAcdContext.getAgentById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateAgent(agent, agentRestInfo); + m_openAcdContext.saveAgent(agent); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Agent failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Agent"); + return; + } + + + // otherwise add new + try { + agent = createOpenAcdAgent(agentRestInfo); + m_openAcdContext.saveAgent(agent); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Agent failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Agent", agent.getId()); + } + + + // DELETE - Delete single Agent + // ---------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdAgent agent; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + agent = m_openAcdContext.getAgentById(idInt); + } + catch (Exception ex) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteAgent(agent); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Agent.", agent.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value is missing."); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdAgentRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private OpenAcdAgentRestInfoFull createAgentRestInfo(int id) throws ResourceException { + OpenAcdAgentRestInfoFull agentRestInfo; + List skillsRestInfo; + List queuesRestInfo; + List clientsRestInfo; + + OpenAcdAgent agent = m_openAcdContext.getAgentById(id); + + skillsRestInfo = createSkillsRestInfo(agent); + queuesRestInfo = createQueuesRestInfo(agent); + clientsRestInfo = createClientRestInfo(agent); + agentRestInfo = new OpenAcdAgentRestInfoFull(agent, skillsRestInfo, queuesRestInfo, clientsRestInfo); + + return agentRestInfo; + } + + private MetadataRestInfo addAgents(List agentsRestInfo, List agents) { + List skillsRestInfo; + List queuesRestInfo; + List clientsRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, agents.size()); + + // create list of agent restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdAgent agent = agents.get(index); + skillsRestInfo = createSkillsRestInfo(agent); + queuesRestInfo = createQueuesRestInfo(agent); + clientsRestInfo = createClientRestInfo(agent); + + OpenAcdAgentRestInfoFull agentRestInfo = new OpenAcdAgentRestInfoFull(agent, skillsRestInfo, queuesRestInfo, clientsRestInfo); + agentsRestInfo.add(agentRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private List createSkillsRestInfo(OpenAcdAgent agent) { + List skillsRestInfo; + OpenAcdSkillRestInfo skillRestInfo; + + // create list of skill restinfos for single group + Set groupSkills = agent.getSkills(); + skillsRestInfo = new ArrayList(groupSkills.size()); + + for (OpenAcdSkill groupSkill : groupSkills) { + skillRestInfo = new OpenAcdSkillRestInfo(groupSkill); + skillsRestInfo.add(skillRestInfo); + } + + return skillsRestInfo; + } + + private List createQueuesRestInfo(OpenAcdAgent agent) { + List queuesRestInfo; + OpenAcdQueueRestInfo queueRestInfo; + + // create list of queue restinfos for single group + Set groupQueues = agent.getQueues(); + queuesRestInfo = new ArrayList(groupQueues.size()); + + for (OpenAcdQueue groupQueue : groupQueues) { + queueRestInfo = new OpenAcdQueueRestInfo(groupQueue); + queuesRestInfo.add(queueRestInfo); + } + + return queuesRestInfo; + } + + private List createClientRestInfo(OpenAcdAgent agent) { + List clientsRestInfo; + OpenAcdClientRestInfo clientRestInfo; + + // create list of queue restinfos for single group + Set groupClients = agent.getClients(); + clientsRestInfo = new ArrayList(groupClients.size()); + + for (OpenAcdClient groupClient : groupClients) { + clientRestInfo = new OpenAcdClientRestInfo(groupClient); + clientsRestInfo.add(clientRestInfo); + } + + return clientsRestInfo; + + } + + private void sortAgents(List agents) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getName(), agent2.getName()); + } + + }); + break; + + case GROUP: + Collections.sort(agents, new Comparator() { + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getAgentGroup(), agent2.getAgentGroup()); + } + }); + break; + + case SECURITY: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getUser().getGroupsNames(), agent2.getUser().getGroupsNames()); + } + + }); + break; + + case USERNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getUser().getUserName(), agent2.getUser().getUserName()); + } + + }); + break; + + case FIRSTNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getFirstName(), agent2.getFirstName()); + } + + }); + break; + + case LASTNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getLastName(), agent2.getLastName()); + } + + }); + break; + + case BRANCH: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getUser().getBranch().getName(), agent2.getUser().getBranch().getName()); + } + + }); + break; + + case EXTENSION: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent1.getUser().getExtension(true), agent2.getUser().getExtension(true)); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getName(), agent1.getName()); + } + + }); + break; + + case GROUP: + Collections.sort(agents, new Comparator() { + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getAgentGroup(), agent1.getAgentGroup()); + } + }); + break; + + case SECURITY: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getUser().getGroupsNames(), agent1.getUser().getGroupsNames()); + } + + }); + break; + + case USERNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getUser().getUserName(), agent1.getUser().getUserName()); + } + + }); + break; + + case FIRSTNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getFirstName(), agent1.getFirstName()); + } + + }); + break; + + case LASTNAME: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getLastName(), agent1.getLastName()); + } + + }); + break; + + case BRANCH: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getUser().getBranch().getName(), agent1.getUser().getBranch().getName()); + } + + }); + break; + + case EXTENSION: + Collections.sort(agents, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdAgent agent1 = (OpenAcdAgent) object1; + OpenAcdAgent agent2 = (OpenAcdAgent) object2; + return RestUtilities.compareIgnoreCaseNullSafe(agent2.getUser().getExtension(true), agent1.getUser().getExtension(true)); + } + + }); + break; + } + } + } + + private void updateAgent(OpenAcdAgent agent, OpenAcdAgentRestInfoFull agentRestInfo) throws ResourceException { + OpenAcdAgentGroup agentGroup; + + agentGroup = getAgentGroup(agentRestInfo); + agent.setGroup(agentGroup); + + agent.setSecurity(agentRestInfo.getSecurity()); + + agent.getSkills().clear(); + + OpenAcdSkill skill; + List skillsRestInfo = agentRestInfo.getSkills(); + for (OpenAcdSkillRestInfo skillRestInfo : skillsRestInfo) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + agent.addSkill(skill); + } + + agent.getQueues().clear(); + + OpenAcdQueue queue; + List queuesRestInfo = agentRestInfo.getQueues(); + for (OpenAcdQueueRestInfo queueRestInfo : queuesRestInfo) { + queue = m_openAcdContext.getQueueById(queueRestInfo.getId()); + agent.addQueue(queue); + } + + agent.getClients().clear(); + + OpenAcdClient client; + List clientsRestInfo = agentRestInfo.getClients(); + for (OpenAcdClientRestInfo clientRestInfo : clientsRestInfo) { + client = m_openAcdContext.getClientById(clientRestInfo.getId()); + agent.addClient(client); + } + + } + + private OpenAcdAgent createOpenAcdAgent(OpenAcdAgentRestInfoFull agentRestInfo) throws ResourceException { + OpenAcdAgent agent = new OpenAcdAgent(); + OpenAcdAgent duplicateAgent = null; + OpenAcdAgentGroup agentGroup; + User user; + + agentGroup = getAgentGroup(agentRestInfo); + agent.setGroup(agentGroup); + + agent.setSecurity(agentRestInfo.getSecurity()); + + user = m_coreContext.getUser(agentRestInfo.getUserId()); + + // check if user is already assigned as agent + duplicateAgent = m_openAcdContext.getAgentByUser(user); + if (duplicateAgent != null) { + throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "User " + user.getId() + " already assigned as agent."); + } + + agent.setUser(user); + + Set skills = new LinkedHashSet(); + List skillsRestInfo = agentRestInfo.getSkills(); + + for (OpenAcdSkillRestInfo skillRestInfo : skillsRestInfo) { + skills.add(m_openAcdContext.getSkillById(skillRestInfo.getId())); + } + + Set queues = new LinkedHashSet(); + List queuesRestInfo = agentRestInfo.getQueues(); + + for (OpenAcdQueueRestInfo queueRestInfo : queuesRestInfo) { + queues.add(m_openAcdContext.getQueueById(queueRestInfo.getId())); + } + + Set clients = new LinkedHashSet(); + List clientsRestInfo = agentRestInfo.getClients(); + + for (OpenAcdClientRestInfo clientRestInfo : clientsRestInfo) { + clients.add(m_openAcdContext.getClientById(clientRestInfo.getId())); + } + + agent.setSkills(skills); + agent.setQueues(queues); + agent.setClients(clients); + + return agent; + } + + private OpenAcdAgentGroup getAgentGroup(OpenAcdAgentRestInfoFull agentRestInfo) throws ResourceException { + OpenAcdAgentGroup agentGroup; + int groupId = 0; + + try { + groupId = agentRestInfo.getGroupId(); + agentGroup = m_openAcdContext.getAgentGroupById(groupId); + } + catch (Exception ex) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "Agent Group ID " + groupId + " not found."); + } + + return agentGroup; + } + + + // REST Representations + // -------------------- + + static class OpenAcdAgentsRepresentation extends XStreamRepresentation { + + public OpenAcdAgentsRepresentation(MediaType mediaType, OpenAcdAgentsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdAgentsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-agent", OpenAcdAgentsBundleRestInfo.class); + xstream.alias("agent", OpenAcdAgentRestInfoFull.class); + xstream.alias("group", OpenAcdAgentGroupRestInfo.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("queue", OpenAcdQueueRestInfo.class); + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + static class OpenAcdAgentRepresentation extends XStreamRepresentation { + + public OpenAcdAgentRepresentation(MediaType mediaType, OpenAcdAgentRestInfoFull object) { + super(mediaType, object); + } + + public OpenAcdAgentRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("agent", OpenAcdAgentRestInfoFull.class); + xstream.alias("group", OpenAcdAgentGroupRestInfo.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("queue", OpenAcdQueueRestInfo.class); + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdAgentsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_agents; + + public OpenAcdAgentsBundleRestInfo(List agents, MetadataRestInfo metadata) { + m_metadata = metadata; + m_agents = agents; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getAgents() { + return m_agents; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdClientsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdClientsResource.java new file mode 100644 index 0000000000..6ed2edfea7 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdClientsResource.java @@ -0,0 +1,461 @@ +/* + * + * OpenAcdClientsResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, I. Wesson, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdClient; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdClientRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdClientsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Client + // ------------------------------------ + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdClientRestInfo clientRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + clientRestInfo = createClientRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Client failed", exception.getLocalizedMessage()); + } + + return new OpenAcdClientRepresentation(variant.getMediaType(), clientRestInfo); + } + + + // if not single, process request for list + List clients = m_openAcdContext.getClients(); + List clientsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortClients(clients); + + // set requested records and get resulting metadata + metadataRestInfo = addClients(clientsRestInfo, clients); + + // create final restinfo + OpenAcdClientsBundleRestInfo clientsBundleRestInfo = new OpenAcdClientsBundleRestInfo(clientsRestInfo, metadataRestInfo); + + return new OpenAcdClientsRepresentation(variant.getMediaType(), clientsBundleRestInfo); + } + + + // PUT - Update or Add single Client + // --------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdClientRepresentation representation = new OpenAcdClientRepresentation(entity); + OpenAcdClientRestInfo clientRestInfo = representation.getObject(); + OpenAcdClient client; + + // validate input for update or create + ValidationInfo validationInfo = validate(clientRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + client = m_openAcdContext.getClientById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateClient(client, clientRestInfo); + m_openAcdContext.saveClient(client); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Client failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Client", client.getId()); + + return; + } + + + // otherwise add new + try { + client = createClient(clientRestInfo); + m_openAcdContext.saveClient(client); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Client failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Client", client.getId()); + } + + + // DELETE - Delete single Client + // ----------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdClient client; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + client = m_openAcdContext.getClientById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteClient(client); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Client", client.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdClientRestInfo restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + String identity = restInfo.getIdentity(); + + for (int i = 0; i < identity.length(); i++) { + if ((!Character.isLetterOrDigit(identity.charAt(i)) && !(Character.getType(identity.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && identity.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Identity' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + return validationInfo; + } + + private OpenAcdClientRestInfo createClientRestInfo(int id) throws ResourceException { + OpenAcdClientRestInfo clientRestInfo; + + try { + OpenAcdClient client = m_openAcdContext.getClientById(id); + clientRestInfo = new OpenAcdClientRestInfo(client); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "ID " + id + " not found."); + } + + return clientRestInfo; + } + + private MetadataRestInfo addClients(List clientsRestInfo, List clients) { + OpenAcdClientRestInfo clientRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, clients.size()); + + // create list of client restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdClient client = clients.get(index); + + clientRestInfo = new OpenAcdClientRestInfo(client); + clientsRestInfo.add(clientRestInfo); + } + + // create metadata about results + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortClients(List clients) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(clients, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdClient client1 = (OpenAcdClient) object1; + OpenAcdClient client2 = (OpenAcdClient) object2; + return client1.getName().compareToIgnoreCase(client2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(clients, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdClient client1 = (OpenAcdClient) object1; + OpenAcdClient client2 = (OpenAcdClient) object2; + return client1.getDescription().compareToIgnoreCase(client2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(clients, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdClient client1 = (OpenAcdClient) object1; + OpenAcdClient client2 = (OpenAcdClient) object2; + return client2.getName().compareToIgnoreCase(client1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(clients, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdClient client1 = (OpenAcdClient) object1; + OpenAcdClient client2 = (OpenAcdClient) object2; + return client2.getDescription().compareToIgnoreCase(client1.getDescription()); + } + + }); + break; + } + } + } + + private void updateClient(OpenAcdClient client, OpenAcdClientRestInfo clientRestInfo) throws ResourceException { + String tempString; + + // do not allow empty name + tempString = clientRestInfo.getName(); + if (!tempString.isEmpty()) { + client.setName(tempString); + } + + client.setDescription(clientRestInfo.getDescription()); + } + + private OpenAcdClient createClient(OpenAcdClientRestInfo clientRestInfo) throws ResourceException { + OpenAcdClient client = new OpenAcdClient(); + + // copy fields from rest info + client.setName(clientRestInfo.getName()); + client.setDescription(clientRestInfo.getDescription()); + client.setIdentity(clientRestInfo.getIdentity()); + + return client; + } + + + // REST Representations + // -------------------- + + static class OpenAcdClientsRepresentation extends XStreamRepresentation { + + public OpenAcdClientsRepresentation(MediaType mediaType, OpenAcdClientsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdClientsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-client", OpenAcdClientsBundleRestInfo.class); + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + static class OpenAcdClientRepresentation extends XStreamRepresentation { + + public OpenAcdClientRepresentation(MediaType mediaType, OpenAcdClientRestInfo object) { + super(mediaType, object); + } + + public OpenAcdClientRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("client", OpenAcdClientRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdClientsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_clients; + + public OpenAcdClientsBundleRestInfo(List clients, MetadataRestInfo metadata) { + m_metadata = metadata; + m_clients = clients; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getClients() { + return m_clients; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdDialStringsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdDialStringsResource.java new file mode 100644 index 0000000000..4e8342ddd4 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdDialStringsResource.java @@ -0,0 +1,540 @@ +/* + * + * OpenAcdDialStringsResource.java - A Restlet to read DialString data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Waseem + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.freeswitch.FreeswitchAction; +import org.sipfoundry.sipxconfig.openacd.OpenAcdCommand; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdDialStringsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Dial Strings + // ------------------------------------------ + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdDialStringRestInfo dialStringRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + dialStringRestInfo = createDialStringRestInfo(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Settings failed", exception.getLocalizedMessage()); + } + + return new OpenAcdDialStringRepresentation(variant.getMediaType(), dialStringRestInfo); + } + + // if not single, process request for list + List dialStrings = new ArrayList(m_openAcdContext.getCommands()); + List dialStringsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortDialStrings(dialStrings); + + // set requested records and get resulting metadata + metadataRestInfo = addDialStrings(dialStringsRestInfo, dialStrings); + + // create final restinfo + OpenAcdDialStringsBundleRestInfo dialStringsBundleRestInfo = new OpenAcdDialStringsBundleRestInfo(dialStringsRestInfo, metadataRestInfo); + + return new OpenAcdDialStringsRepresentation(variant.getMediaType(), dialStringsBundleRestInfo); + } + + + // PUT - Update or Add single Dial String + // -------------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdDialStringRepresentation representation = new OpenAcdDialStringRepresentation(entity); + OpenAcdDialStringRestInfo dialStringRestInfo = representation.getObject(); + OpenAcdCommand dialString = null; + + ValidationInfo validationInfo = validate(dialStringRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + dialString = (OpenAcdCommand) m_openAcdContext.getExtensionById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateDialString(dialString, dialStringRestInfo); + m_openAcdContext.saveExtension(dialString); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Setting failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Settings", dialString.getId()); + + return; + } + + + // otherwise add new + try { + dialString = createDialString(dialStringRestInfo); + m_openAcdContext.saveExtension(dialString); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Dial String failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Setting", dialString.getId()); + } + + + // DELETE - Delete single Dial String + // ---------------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdCommand dialString; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + dialString = (OpenAcdCommand) m_openAcdContext.getExtensionById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteExtension(dialString); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Client", dialString.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdDialStringRestInfo restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private OpenAcdDialStringRestInfo createDialStringRestInfo(int id) throws ResourceException { + OpenAcdDialStringRestInfo dialStringRestInfo; + + try { + OpenAcdCommand dialString = (OpenAcdCommand) m_openAcdContext.getExtensionById(id); + dialStringRestInfo = createDialStringRestInfo(dialString); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "ID " + id + " not found."); + } + + return dialStringRestInfo; + } + + private OpenAcdDialStringRestInfo createDialStringRestInfo(OpenAcdCommand dialString) { + OpenAcdDialStringRestInfo dialStringRestInfo; + List dialStringActionRestInfo; + + dialStringActionRestInfo = createDialStringActionRestInfo(dialString); + dialStringRestInfo = new OpenAcdDialStringRestInfo(dialString, dialStringActionRestInfo); + + return dialStringRestInfo; + + } + + private List createDialStringActionRestInfo(OpenAcdCommand dialString) { + OpenAcdDialStringActionRestInfo dialStringActionRestInfo; + List customActions = new ArrayList(); + + List actions = dialString.getLineActions(); + + for (FreeswitchAction action : actions) { + dialStringActionRestInfo = new OpenAcdDialStringActionRestInfo(action); + customActions.add(dialStringActionRestInfo); + } + + return customActions; + } + + private MetadataRestInfo addDialStrings(List dialStringsRestInfo, List dialStrings) { + OpenAcdDialStringRestInfo dialStringRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, dialStrings.size()); + + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdCommand dialString = dialStrings.get(index); + + dialStringRestInfo = createDialStringRestInfo(dialString); + dialStringsRestInfo.add(dialStringRestInfo); + } + + // create metadata about results + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortDialStrings(List dialStrings) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(dialStrings, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdCommand dialString1 = (OpenAcdCommand) object1; + OpenAcdCommand dialString2 = (OpenAcdCommand) object2; + return dialString1.getName().compareToIgnoreCase(dialString2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(dialStrings, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdCommand dialString1 = (OpenAcdCommand) object1; + OpenAcdCommand dialString2 = (OpenAcdCommand) object2; + return dialString1.getDescription().compareToIgnoreCase(dialString2.getDescription()); + } + + }); + break; + + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(dialStrings, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdCommand dialString1 = (OpenAcdCommand) object1; + OpenAcdCommand dialString2 = (OpenAcdCommand) object2; + return dialString2.getName().compareToIgnoreCase(dialString1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(dialStrings, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdCommand dialString1 = (OpenAcdCommand) object1; + OpenAcdCommand dialString2 = (OpenAcdCommand) object2; + return dialString2.getDescription().compareToIgnoreCase(dialString1.getDescription()); + } + + }); + break; + } + } + } + + private void updateDialString(OpenAcdCommand dialString, OpenAcdDialStringRestInfo dialStringRestInfo) throws ResourceException { + + dialString.setName(dialStringRestInfo.getName()); + dialString.setEnabled(dialStringRestInfo.getEnabled()); + dialString.setDescription(dialStringRestInfo.getDescription()); + dialString.getNumberCondition().setExpression(dialStringRestInfo.getExtension()); + + for (OpenAcdDialStringActionRestInfo actionRestInfo : dialStringRestInfo.getActions()) { + dialString.getNumberCondition().addAction(OpenAcdCommand.createAction(actionRestInfo.getApplication(), actionRestInfo.getApplication())); + } + } + + private OpenAcdCommand createDialString(OpenAcdDialStringRestInfo dialStringRestInfo) throws ResourceException { + OpenAcdCommand dialString = m_openAcdContext.newOpenAcdCommand(); + dialString.addCondition(OpenAcdCommand.createLineCondition()); + String tempString; + + if (!(tempString = dialStringRestInfo.getName()).isEmpty()) { + dialString.setName(tempString); + } + + dialString.setEnabled(dialStringRestInfo.getEnabled()); + dialString.setDescription(dialStringRestInfo.getDescription()); + dialString.getNumberCondition().setExpression(dialStringRestInfo.getExtension()); + + for (OpenAcdDialStringActionRestInfo actionRestInfo : dialStringRestInfo.getActions()) { + dialString.getNumberCondition().addAction(OpenAcdCommand.createAction(actionRestInfo.getApplication(), actionRestInfo.getApplication())); + } + + return dialString; + } + + + // REST Representations + // -------------------- + + static class OpenAcdDialStringsRepresentation extends XStreamRepresentation { + + public OpenAcdDialStringsRepresentation(MediaType mediaType, OpenAcdDialStringsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdDialStringsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-dial-string", OpenAcdDialStringsBundleRestInfo.class); + xstream.alias("dialString", OpenAcdDialStringRestInfo.class); + xstream.alias("action", OpenAcdDialStringActionRestInfo.class); + } + } + + static class OpenAcdDialStringRepresentation extends XStreamRepresentation { + + public OpenAcdDialStringRepresentation(MediaType mediaType, OpenAcdDialStringRestInfo dialStringsBundleRestInfo) { + super(mediaType, dialStringsBundleRestInfo); + } + + public OpenAcdDialStringRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("dialString", OpenAcdDialStringRestInfo.class); + xstream.alias("action", OpenAcdDialStringActionRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdDialStringsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_dialStrings; + + public OpenAcdDialStringsBundleRestInfo(List dialStrings, MetadataRestInfo metadata) { + m_metadata = metadata; + m_dialStrings = dialStrings; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getDialStrings() { + return m_dialStrings; + } + } + + static class OpenAcdDialStringRestInfo { + private final int m_id; + private final String m_name; + private final boolean m_enabled; + private final String m_description; + private final String m_extension; + private final List m_actions; + + public OpenAcdDialStringRestInfo(OpenAcdCommand dial, List dialStringActionsRestInfo) { + m_id = dial.getId(); + m_name = dial.getName(); + m_enabled = dial.isEnabled(); + m_description = dial.getDescription(); + m_extension = dial.getExtension(); + m_actions = dialStringActionsRestInfo; + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public boolean getEnabled() { + return m_enabled; + } + + public String getDescription() { + return m_description; + } + + public String getExtension() { + return m_extension; + } + + public List getActions() { + return m_actions; + } + } + + static class OpenAcdDialStringActionRestInfo { + private final String m_application; + private final String m_data; + + public OpenAcdDialStringActionRestInfo(FreeswitchAction action) { + m_application = action.getApplication(); + m_data = action.getData(); + } + + public String getApplication() { + return m_application; + } + + public String getData() { + return m_data; + } + } + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdLinesResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdLinesResource.java new file mode 100644 index 0000000000..2aa5f0f349 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdLinesResource.java @@ -0,0 +1,866 @@ +/* + * + * OpenAcdLinesResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, I. Wesson, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.freeswitch.FreeswitchAction; +import org.sipfoundry.sipxconfig.openacd.OpenAcdClient; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdLine; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueue; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdClientRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdQueueRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +// OpenAcdLines are different +// -------------------------- +// Lines is inconsistent with other OpenACD objects. +// OpenAcdContext does not contain functions for getLineById(), saveLine() or removeLine(). +// It appears the OpenAcdExtension object is used for lines, and it has all the above functions +// so this API will appear slightly different than other APIs, although attempts have been made to preserve general structure. +public class OpenAcdLinesResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + enum SortField { + NAME, DESCRIPTION, EXTENSION, DIDNUMBER, QUEUE, CLIENT, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + + // GET - Retrieve all and single item + // ---------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdLineRestInfo lineRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + lineRestInfo = createLineRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Line failed", exception.getLocalizedMessage()); + } + + return new OpenAcdLineRepresentation(variant.getMediaType(), lineRestInfo); + } + + + // if not single, process request for list + List lines = new ArrayList(m_openAcdContext.getLines()); + List linesRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortLines(lines); + + // set requested records and get resulting metadata + metadataRestInfo = addLines(linesRestInfo, lines); + + // create final restinfo + OpenAcdLinesBundleRestInfo linesBundleRestInfo = new OpenAcdLinesBundleRestInfo(linesRestInfo, metadataRestInfo); + + return new OpenAcdLinesRepresentation(variant.getMediaType(), linesBundleRestInfo); + } + + + // PUT - Update or Add single item + // ------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdLineRepresentation representation = new OpenAcdLineRepresentation(entity); + OpenAcdLineRestInfo lineRestInfo = representation.getObject(); + OpenAcdLine line = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(lineRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + line = (OpenAcdLine) m_openAcdContext.getExtensionById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateLine(line, lineRestInfo); + m_openAcdContext.saveExtension(line); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Line failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Line", line.getId()); + + return; + } + + + // otherwise add new + try { + line = createLine(lineRestInfo); + m_openAcdContext.saveExtension(line); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Line failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Line", line.getId()); + } + + + // DELETE - Delete single item + // --------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdLine line; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + line = (OpenAcdLine) m_openAcdContext.getExtensionById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteExtension(line); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Line", line.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdLineRestInfo restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + String name = restInfo.getName(); + String ext = restInfo.getExtension(); + String did = restInfo.getDIDNumber(); + String alias = restInfo.getAlias(); + + if (!StringUtils.isEmpty(name)) { + for (int i = 0; i < name.length(); i++) { + if (name.charAt(i) == ' ') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Name' must only contain letters, numbers, dashes, underscores, and symbols"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + if (!StringUtils.isEmpty(ext)) { + for (int i = 0; i < ext.length(); i++) { + if ((!Character.isDigit(ext.charAt(i)))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Extension' must only contain numbers"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + if (!StringUtils.isEmpty(did)) { + for (int i = 0; i < did.length(); i++) { + if ((!Character.isDigit(did.charAt(i)))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'DID Number' must only contain numbers"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + if (!StringUtils.isEmpty(alias)) { + for (int i = 0; i < alias.length(); i++) { + if ((!Character.isDigit(alias.charAt(i)))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Alias' must only contain numbers"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + return validationInfo; + } + + // parses OpenAcdLine contents instead of just an id because line is so different + private OpenAcdLineActionsBundleRestInfo createLineActionsBundleRestInfo(OpenAcdLine line) { + OpenAcdLineActionsBundleRestInfo lineActionsBundleRestInfo; + OpenAcdQueueRestInfo queueRestInfo; + OpenAcdClientRestInfo clientRestInfo; + List customActions = new ArrayList(); + OpenAcdLineActionRestInfo customActionRestInfo; + + OpenAcdQueue queue; + String queueName = ""; + OpenAcdClient client; + String clientIdentity = ""; + Boolean allowVoicemail = false; + String allowVoicemailString = "false"; + Boolean isFsSet = false; + Boolean isAgentSet = false; + String answerSupervisionType = ""; + String welcomeMessage = ""; + + List actions = line.getLineActions(); + for (FreeswitchAction action : actions) { + String application = action.getApplication(); + String data = action.getData(); + + if (StringUtils.equals(application, FreeswitchAction.PredefinedAction.answer.toString())) { + isFsSet = true; + } + else if (StringUtils.contains(data, OpenAcdLine.ERLANG_ANSWER)) { + isAgentSet = true; + } + else if (StringUtils.contains(data, OpenAcdLine.Q)) { + queueName = StringUtils.removeStart(data, OpenAcdLine.Q); + } + else if (StringUtils.contains(data, OpenAcdLine.BRAND)) { + clientIdentity = StringUtils.removeStart(data, OpenAcdLine.BRAND); + } + else if (StringUtils.contains(data, OpenAcdLine.ALLOW_VOICEMAIL)) { + allowVoicemailString = StringUtils.removeStart(data, OpenAcdLine.ALLOW_VOICEMAIL); + } + else if (StringUtils.equals(application, FreeswitchAction.PredefinedAction.playback.toString())) { + // web ui only displays filename and appends audio directory + //welcomeMessage = StringUtils.removeStart(data, m_openAcdContext.getSettings().getAudioDirectory() + "/"); + welcomeMessage = data; + } + else { + customActionRestInfo = new OpenAcdLineActionRestInfo(action); + customActions.add(customActionRestInfo); + } + } + + queue = m_openAcdContext.getQueueByName(queueName); + queueRestInfo = new OpenAcdQueueRestInfo(queue); + + client = m_openAcdContext.getClientByIdentity(clientIdentity); + clientRestInfo = new OpenAcdClientRestInfo(client); + + allowVoicemail = Boolean.parseBoolean(allowVoicemailString); + + if (isFsSet) { + answerSupervisionType = "FS"; + } + else if (isAgentSet) { + answerSupervisionType = "AGENT"; + } + else { + answerSupervisionType = "ACD"; + } + + lineActionsBundleRestInfo = new OpenAcdLineActionsBundleRestInfo(queueRestInfo, clientRestInfo, allowVoicemail, customActions, answerSupervisionType, welcomeMessage); + + return lineActionsBundleRestInfo; + } + + private OpenAcdLineRestInfo createLineRestInfo(int id) { + OpenAcdLineRestInfo lineRestInfo; + + OpenAcdLine line = (OpenAcdLine) m_openAcdContext.getExtensionById(id); + lineRestInfo = createLineRestInfo(line); + + return lineRestInfo; + } + + private OpenAcdLineRestInfo createLineRestInfo(OpenAcdLine line) { + OpenAcdLineRestInfo lineRestInfo; + OpenAcdLineActionsBundleRestInfo lineActionsBundleRestInfo; + + lineActionsBundleRestInfo = createLineActionsBundleRestInfo(line); + lineRestInfo = new OpenAcdLineRestInfo(line, lineActionsBundleRestInfo); + + return lineRestInfo; + } + + private MetadataRestInfo addLines(List linesRestInfo, List lines) { + OpenAcdLineRestInfo lineRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, lines.size()); + + // create list of line restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdLine line = lines.get(index); + + lineRestInfo = createLineRestInfo(line); + linesRestInfo.add(lineRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortLines(List lines) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line1.getName(), line2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line1.getDescription(), line2.getDescription()); + } + + }); + break; + + case EXTENSION: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line1.getExtension(), line2.getExtension()); + } + + }); + break; + + case DIDNUMBER: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line1.getDid(), line2.getDid()); + } + + }); + break; + + case QUEUE: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(createLineRestInfo(line1).m_actions.getQueue().getName(), createLineRestInfo(line2).m_actions.getQueue().getName()); + } + + }); + break; + + case CLIENT: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(createLineRestInfo(line1).m_actions.getClient().getIdentity(), createLineRestInfo(line2).m_actions.getClient().getIdentity()); + } + + }); + break; + + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line2.getName(), line1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line2.getDescription(), line1.getDescription()); + } + + }); + break; + + case EXTENSION: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line2.getExtension(), line1.getExtension()); + } + + }); + break; + + case DIDNUMBER: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(line2.getDid(), line1.getDid()); + } + + }); + break; + + case QUEUE: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(createLineRestInfo(line2).m_actions.getQueue().getName(), createLineRestInfo(line1).m_actions.getQueue().getName()); + } + + }); + break; + + case CLIENT: + Collections.sort(lines, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdLine line1 = (OpenAcdLine) object1; + OpenAcdLine line2 = (OpenAcdLine) object2; + return RestUtilities.compareIgnoreCaseNullSafe(createLineRestInfo(line2).m_actions.getClient().getIdentity(), createLineRestInfo(line1).m_actions.getClient().getIdentity()); + } + + }); + break; + } + } + } + + private void updateLine(OpenAcdLine line, OpenAcdLineRestInfo lineRestInfo) throws ResourceException { + String tempString; + + // do not allow empty name + tempString = lineRestInfo.getName(); + if (!tempString.isEmpty()) { + line.setName(tempString); + } + + line.setDid(lineRestInfo.getDIDNumber()); + line.setDescription(lineRestInfo.getDescription()); + line.setAlias(lineRestInfo.getAlias()); + + // set standard actions + line.getNumberCondition().getActions().clear(); + + // answer supervision type is an integer code from OpenAcdLine + line.getNumberCondition().addAction(OpenAcdLine.createAnswerAction(getAnswerSupervisionCode(lineRestInfo.getActions().getAnswerSupervisionType()))); + line.getNumberCondition().addAction(OpenAcdLine.createVoicemailAction(lineRestInfo.getActions().getAllowVoicemail())); + line.getNumberCondition().addAction(OpenAcdLine.createQueueAction(lineRestInfo.getActions().getQueue().getName())); + line.getNumberCondition().addAction(OpenAcdLine.createClientAction(lineRestInfo.getActions().getClient().getIdentity())); + + // web ui only displays filename and appends audio directory + //line.getNumberCondition().addAction(OpenAcdLine.createPlaybackAction(m_openAcdContext.getSettings().getAudioDirectory() + "/" + lineRestInfo.getActions().getWelcomeMessage())); + line.getNumberCondition().addAction(OpenAcdLine.createPlaybackAction(lineRestInfo.getActions().getWelcomeMessage())); + + // set custom actions + for (OpenAcdLineActionRestInfo actionRestInfo : lineRestInfo.getActions().getCustomActions()) { + line.getNumberCondition().addAction(OpenAcdLine.createAction(actionRestInfo.getApplication(), actionRestInfo.getData())); + } + + // "Expression" is the extension number, which may be a regular expression if regex is set + line.getNumberCondition().setExpression(lineRestInfo.getExtension()); + line.getNumberCondition().setRegex(lineRestInfo.getRegex()); + } + + private OpenAcdLine createLine(OpenAcdLineRestInfo lineRestInfo) throws ResourceException { + // special steps to obtain new line (cannot just "new") + OpenAcdLine line = m_openAcdContext.newOpenAcdLine(); + line.addCondition(OpenAcdLine.createLineCondition()); + + // copy fields from rest info + line.setName(lineRestInfo.getName()); + line.setDid(lineRestInfo.getDIDNumber()); + line.setDescription(lineRestInfo.getDescription()); + line.setAlias(lineRestInfo.getAlias()); + + // set standard actions + line.getNumberCondition().getActions().clear(); + + // answer supervision type is an integer code from OpenAcdLine + line.getNumberCondition().addAction(OpenAcdLine.createAnswerAction(getAnswerSupervisionCode(lineRestInfo.getActions().getAnswerSupervisionType()))); + line.getNumberCondition().addAction(OpenAcdLine.createVoicemailAction(lineRestInfo.getActions().getAllowVoicemail())); + line.getNumberCondition().addAction(OpenAcdLine.createQueueAction(lineRestInfo.getActions().getQueue().getName())); + line.getNumberCondition().addAction(OpenAcdLine.createClientAction(lineRestInfo.getActions().getClient().getIdentity())); + + // web ui only displays filename and appends audio directory + //line.getNumberCondition().addAction(OpenAcdLine.createPlaybackAction(m_openAcdContext.getSettings().getAudioDirectory() + "/" + lineRestInfo.getActions().getWelcomeMessage())); + line.getNumberCondition().addAction(OpenAcdLine.createPlaybackAction(lineRestInfo.getActions().getWelcomeMessage())); + + // set custom actions + for (OpenAcdLineActionRestInfo actionRestInfo : lineRestInfo.getActions().getCustomActions()) { + line.getNumberCondition().addAction(OpenAcdLine.createAction(actionRestInfo.getApplication(), actionRestInfo.getData())); + } + + // "Expression" is the extension number, which may be a regular expression if regex is set + line.getNumberCondition().setExpression(lineRestInfo.getExtension()); + line.getNumberCondition().setRegex(lineRestInfo.getRegex()); + + return line; + } + + private Integer getAnswerSupervisionCode(String answerSupervisionType) { + Integer answerSupervisionCode; + + if (StringUtils.equalsIgnoreCase(answerSupervisionType, "FS")) { + answerSupervisionCode = OpenAcdLine.FS; + } + else if (StringUtils.equalsIgnoreCase(answerSupervisionType, "AGENT")) { + answerSupervisionCode = OpenAcdLine.AGENT; + } + else { + answerSupervisionCode = OpenAcdLine.ACD; + } + + return answerSupervisionCode; + } + + + // REST Representations + // -------------------- + + static class OpenAcdLinesRepresentation extends XStreamRepresentation { + + public OpenAcdLinesRepresentation(MediaType mediaType, OpenAcdLinesBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdLinesRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-line", OpenAcdLinesBundleRestInfo.class); + xstream.alias("line", OpenAcdLineRestInfo.class); + xstream.alias("action", OpenAcdLineActionRestInfo.class); + } + } + + static class OpenAcdLineRepresentation extends XStreamRepresentation { + + public OpenAcdLineRepresentation(MediaType mediaType, OpenAcdLineRestInfo object) { + super(mediaType, object); + } + + public OpenAcdLineRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("line", OpenAcdLineRestInfo.class); + xstream.alias("action", OpenAcdLineActionRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdLinesBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_lines; + + public OpenAcdLinesBundleRestInfo(List lines, MetadataRestInfo metadata) { + m_metadata = metadata; + m_lines = lines; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getLines() { + return m_lines; + } + } + + static class OpenAcdLineRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + private final String m_extension; + private final boolean m_regex; + private final String m_didnumber; + private final String m_alias; + private final OpenAcdLineActionsBundleRestInfo m_actions; + + public OpenAcdLineRestInfo(OpenAcdLine line, OpenAcdLineActionsBundleRestInfo lineActionsRestInfo) { + m_id = line.getId(); + m_name = line.getName(); + m_description = line.getDescription(); + m_extension = line.getExtension(); + m_regex = line.getRegex(); + m_didnumber = line.getDid(); + m_alias = line.getAlias(); + m_actions = lineActionsRestInfo; + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + + public String getExtension() { + return m_extension; + } + + public boolean getRegex() { + return m_regex; + } + + public String getDIDNumber() { + return m_didnumber; + } + + public String getAlias() { + return m_alias; + } + + public OpenAcdLineActionsBundleRestInfo getActions() { + return m_actions; + } + } + + static class OpenAcdLineActionRestInfo { + private final String m_application; + private final String m_data; + + public OpenAcdLineActionRestInfo(FreeswitchAction action) { + m_application = action.getApplication(); + m_data = action.getData(); + } + + public String getApplication() { + return m_application; + } + + public String getData() { + return m_data; + } + } + + static class OpenAcdLineActionsBundleRestInfo { + // standard (required?) actions + private final OpenAcdQueueRestInfo m_queue; + private final OpenAcdClientRestInfo m_client; + private final boolean m_allowVoicemail; + private final String m_answerSupervisionType; + private final String m_welcomeMessage; + + // additional (custom) actions + private final List m_customActions; + + public OpenAcdLineActionsBundleRestInfo(OpenAcdQueueRestInfo queueRestInfo, OpenAcdClientRestInfo clientRestInfo, boolean allowVoicemail, List customActions, String answerSupervisionType, String welcomeMessage) { + m_queue = queueRestInfo; + m_client = clientRestInfo; + m_allowVoicemail = allowVoicemail; + m_customActions = customActions; + m_answerSupervisionType = answerSupervisionType; + m_welcomeMessage = welcomeMessage; + } + + public OpenAcdQueueRestInfo getQueue() { + return m_queue; + } + + public OpenAcdClientRestInfo getClient() { + return m_client; + } + + public boolean getAllowVoicemail() { + return m_allowVoicemail; + } + + public String getAnswerSupervisionType() { + return m_answerSupervisionType; + } + + public String getWelcomeMessage() { + return m_welcomeMessage; + } + + public List getCustomActions() { + return m_customActions; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueueGroupsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueueGroupsResource.java new file mode 100644 index 0000000000..b6d4917e8c --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueueGroupsResource.java @@ -0,0 +1,724 @@ +/* + * + * OpenAcdQueueGroupsResource.java - A Restlet to read group data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgentGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueueGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeAction; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeCondition; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeStep; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdAgentGroupRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdQueueGroupRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeActionRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeConditionRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeStepRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdQueueGroupsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve Groups and single Group + // -------------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdQueueGroupRestInfoFull queueGroupRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + queueGroupRestInfo = createQueueGroupRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), ResponseCode.ERROR_READ_FAILED, "Read Queue Group failed", exception.getLocalizedMessage()); + } + + return new OpenAcdQueueGroupRepresentation(variant.getMediaType(), queueGroupRestInfo); + } + + + // if not single, process request for all + List queueGroups = m_openAcdContext.getQueueGroups(); + List queueGroupsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortQueueGroups(queueGroups); + + // set requested based on pagination and get resulting metadata + metadataRestInfo = addQueueGroups(queueGroupsRestInfo, queueGroups); + + // create final restinfo + OpenAcdQueueGroupsBundleRestInfo queueGroupsBundleRestInfo = new OpenAcdQueueGroupsBundleRestInfo(queueGroupsRestInfo, metadataRestInfo); + + return new OpenAcdQueueGroupsRepresentation(variant.getMediaType(), queueGroupsBundleRestInfo); + } + + + // PUT - Update or Add single Group + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get group from body + OpenAcdQueueGroupRepresentation representation = new OpenAcdQueueGroupRepresentation(entity); + OpenAcdQueueGroupRestInfoFull queueGroupRestInfo = representation.getObject(); + OpenAcdQueueGroup queueGroup; + + // validate input for update or create + ValidationInfo validationInfo = validate(queueGroupRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update a single group + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + queueGroup = m_openAcdContext.getQueueGroupById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing group + try { + updateQueueGroup(queueGroup, queueGroupRestInfo); + m_openAcdContext.saveQueueGroup(queueGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_WRITE_FAILED, "Update Queue Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), ResponseCode.SUCCESS_UPDATED, "Updated Queue", queueGroup.getId()); + + return; + } + + + // otherwise add new agent group + try { + queueGroup = createQueueGroup(queueGroupRestInfo); + m_openAcdContext.saveQueueGroup(queueGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Queue Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Queue Group", queueGroup.getId()); + } + + + // DELETE - Delete single Group + // -------------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdQueueGroup queueGroup; + + // get id then delete a single group + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + queueGroup = m_openAcdContext.getQueueGroupById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteQueueGroup(queueGroup); + + RestUtilities.setResponse(getResponse(), ResponseCode.SUCCESS_DELETED, "Deleted Queue Group", queueGroup.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdQueueGroupRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + List relation = Arrays.asList("is", "isNot"); + List condition1 = Arrays.asList("available_agents", "eligible_agents", "calls_queued", "queue_position", "hour", "weekday", "client_calls_queued"); + List condition2 = Arrays.asList("ticks", "client", "media_type", "caller_id", "caller_name"); + List equalityRelation = Arrays.asList("is", "greater", "less"); + List mediaValues = Arrays.asList("voice", "email", "voicemail", "chat"); + + String name = restInfo.getName(); + + // rest mods the hours with 24 to give a new hour, so allow over 23 + + for (int i = 0; i < name.length(); i++) { + if ((!Character.isLetterOrDigit(name.charAt(i)) && !(Character.getType(name.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && name.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Name' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + for (int i = 0; i < restInfo.getSteps().size(); i++) { + if (restInfo.getSteps().get(i).getAction().getAction().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action' cannot be empty and must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + + if (restInfo.getSteps().get(i).getAction().getAction().equals("announce") || restInfo.getSteps().get(i).getAction().getAction().equals("set_priority")) { + if (restInfo.getSteps().get(i).getAction().getActionValue().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action Value' cannot be empty and must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + for (int j = 0; j < m_openAcdContext.getClients().size(); j++) { + if (m_openAcdContext.getClients().get(j).getIdentity().equals(restInfo.getSteps().get(i).getAction().getActionValue())) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: Client Does not Exist"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getAction().getAction().equals("set_priority")) { + for (int j = 0; j < restInfo.getSteps().get(i).getAction().getActionValue().length(); j++) { + char c = restInfo.getSteps().get(i).getAction().getActionValue().charAt(j); + if (!Character.isDigit(c) && c != '*') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action Value' must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + for (int k = 0; k < restInfo.getSteps().get(i).getConditions().size(); k++) { + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().isEmpty() || restInfo.getSteps().get(i).getConditions().get(k).getRelation().isEmpty() || restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Condtion' cannot be empty"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + + } + + if (condition2.contains(restInfo.getSteps().get(i).getConditions().get(k).getCondition())) { + if (!(relation.contains(restInfo.getSteps().get(i).getConditions().get(k).getRelation()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Relation' must only be 'is' or 'isNot'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + + } + + if (condition1.contains(restInfo.getSteps().get(i).getConditions().get(k).getCondition())) { + if (!(equalityRelation.contains(restInfo.getSteps().get(i).getConditions().get(k).getRelation()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Relation' must only be 'is', 'greater', or 'less'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("media_type")) { + if (!(mediaValues.contains(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' can only be 'voice', 'email', 'voicemail', or 'chat'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("weekday")) { + if (Integer.parseInt(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()) < 1 || Integer.parseInt(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()) > 7) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' must be between 1 and 7"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (!(restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("caller_id") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("caller_name") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("media_type") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("client"))) { + for (int j = 0; j < restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().length(); j++) { + char c = restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().charAt(j); + if (!Character.isDigit(c) && c != '*') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + } + } + + return validationInfo; + } + + private OpenAcdQueueGroupRestInfoFull createQueueGroupRestInfo(int id) throws ResourceException { + OpenAcdQueueGroupRestInfoFull queueGroupRestInfo; + List skillsRestInfo; + List agentGroupRestInfo; + List recipeStepRestInfo; + + OpenAcdQueueGroup queueGroup = m_openAcdContext.getQueueGroupById(id); + skillsRestInfo = createSkillsRestInfo(queueGroup.getSkills()); + agentGroupRestInfo = createAgentGroupsRestInfo(queueGroup); + recipeStepRestInfo = createRecipeStepsRestInfo(queueGroup); + queueGroupRestInfo = new OpenAcdQueueGroupRestInfoFull(queueGroup, skillsRestInfo, agentGroupRestInfo, recipeStepRestInfo); + + return queueGroupRestInfo; + } + + private List createSkillsRestInfo(Set groupSkills) { + List skillsRestInfo; + OpenAcdSkillRestInfo skillRestInfo; + + // create list of skill restinfos for single group + skillsRestInfo = new ArrayList(groupSkills.size()); + + for (OpenAcdSkill groupSkill : groupSkills) { + skillRestInfo = new OpenAcdSkillRestInfo(groupSkill); + skillsRestInfo.add(skillRestInfo); + } + + return skillsRestInfo; + } + + private List createAgentGroupsRestInfo(OpenAcdQueueGroup queueGroup) { + List agentGroupsRestInfo; + OpenAcdAgentGroupRestInfo agentGroupRestInfo; + + // create list of agent group restinfos for single group + Set groupAgentGroups = queueGroup.getAgentGroups(); + agentGroupsRestInfo = new ArrayList(groupAgentGroups.size()); + + for (OpenAcdAgentGroup groupAgentGroup : groupAgentGroups) { + agentGroupRestInfo = new OpenAcdAgentGroupRestInfo(groupAgentGroup); + agentGroupsRestInfo.add(agentGroupRestInfo); + } + + return agentGroupsRestInfo; + } + + private List createRecipeStepsRestInfo(OpenAcdQueueGroup queueGroup) { + List recipeStepsRestInfo; + OpenAcdRecipeStepRestInfo recipeStepRestInfo; + + Set groupRecipeSteps = queueGroup.getSteps(); + recipeStepsRestInfo = new ArrayList(groupRecipeSteps.size()); + + for (OpenAcdRecipeStep groupRecipeStep : groupRecipeSteps) { + recipeStepRestInfo = new OpenAcdRecipeStepRestInfo(groupRecipeStep, createRecipeActionRestInfo(groupRecipeStep), createRecipeConditionsRestInfo(groupRecipeStep)); + recipeStepsRestInfo.add(recipeStepRestInfo); + } + return recipeStepsRestInfo; + } + + private OpenAcdRecipeActionRestInfo createRecipeActionRestInfo(OpenAcdRecipeStep step) { + OpenAcdRecipeActionRestInfo recipeActionRestInfo; + List skillsRestInfo; + + // get skills associated with action + skillsRestInfo = createSkillsRestInfo(step.getAction().getSkills()); + recipeActionRestInfo = new OpenAcdRecipeActionRestInfo(step.getAction(), skillsRestInfo); + + return recipeActionRestInfo; + } + + private List createRecipeConditionsRestInfo(OpenAcdRecipeStep step) { + List recipeConditionsRestInfo; + OpenAcdRecipeConditionRestInfo recipeConditionRestInfo; + + + List groupRecipeConditions = step.getConditions(); + recipeConditionsRestInfo = new ArrayList(groupRecipeConditions.size()); + + for (OpenAcdRecipeCondition groupRecipeCondition : groupRecipeConditions) { + recipeConditionRestInfo = new OpenAcdRecipeConditionRestInfo(groupRecipeCondition); + recipeConditionsRestInfo.add(recipeConditionRestInfo); + } + + return recipeConditionsRestInfo; + } + + private MetadataRestInfo addQueueGroups(List queueGroupsRestInfo, List queueGroups) { + List skillsRestInfo; + List agentGroupRestInfo; + List recipeStepRestInfo; + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, queueGroups.size()); + + // create list of group restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdQueueGroup queueGroup = queueGroups.get(index); + + skillsRestInfo = createSkillsRestInfo(queueGroup.getSkills()); + agentGroupRestInfo = createAgentGroupsRestInfo(queueGroup); + recipeStepRestInfo = createRecipeStepsRestInfo(queueGroup); + + OpenAcdQueueGroupRestInfoFull queueGroupRestInfo = new OpenAcdQueueGroupRestInfoFull(queueGroup, skillsRestInfo, agentGroupRestInfo, recipeStepRestInfo); + queueGroupsRestInfo.add(queueGroupRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortQueueGroups(List queueGroups) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(queueGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueueGroup queueGroup1 = (OpenAcdQueueGroup) object1; + OpenAcdQueueGroup queueGroup2 = (OpenAcdQueueGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queueGroup1.getName(), queueGroup2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(queueGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueueGroup queueGroup1 = (OpenAcdQueueGroup) object1; + OpenAcdQueueGroup queueGroup2 = (OpenAcdQueueGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queueGroup1.getDescription(), queueGroup2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(queueGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueueGroup queueGroup1 = (OpenAcdQueueGroup) object1; + OpenAcdQueueGroup queueGroup2 = (OpenAcdQueueGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queueGroup2.getName(), queueGroup1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(queueGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueueGroup queueGroup1 = (OpenAcdQueueGroup) object1; + OpenAcdQueueGroup queueGroup2 = (OpenAcdQueueGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queueGroup2.getDescription(), queueGroup1.getDescription()); + } + + }); + break; + } + } + } + + private void updateQueueGroup(OpenAcdQueueGroup queueGroup, OpenAcdQueueGroupRestInfoFull queueGroupRestInfo) { + String tempString; + + // do not allow empty name + tempString = queueGroupRestInfo.getName(); + if (!tempString.isEmpty()) { + queueGroup.setName(tempString); + } + + queueGroup.setDescription(queueGroupRestInfo.getDescription()); + + addLists(queueGroup, queueGroupRestInfo); + } + + private OpenAcdQueueGroup createQueueGroup(OpenAcdQueueGroupRestInfoFull queueGroupRestInfo) { + OpenAcdQueueGroup queueGroup = new OpenAcdQueueGroup(); + + // copy fields from rest info + queueGroup.setName(queueGroupRestInfo.getName()); + queueGroup.setDescription(queueGroupRestInfo.getDescription()); + + addLists(queueGroup, queueGroupRestInfo); + + return queueGroup; + } + + private void addLists(OpenAcdQueueGroup queueGroup, OpenAcdQueueGroupRestInfoFull queueGroupRestInfo) { + // remove all skills + queueGroup.getSkills().clear(); + + // set skills + OpenAcdSkill skill; + List skillsRestInfo = queueGroupRestInfo.getSkills(); + for (OpenAcdSkillRestInfo skillRestInfo : skillsRestInfo) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + queueGroup.addSkill(skill); + } + + // remove all agent groups + queueGroup.getAgentGroups().clear(); + + // set agent groups + OpenAcdAgentGroup agentGroup; + List agentGroupsRestInfo = queueGroupRestInfo.getAgentGroups(); + for (OpenAcdAgentGroupRestInfo agentGroupRestInfo : agentGroupsRestInfo) { + agentGroup = m_openAcdContext.getAgentGroupById(agentGroupRestInfo.getId()); + queueGroup.addAgentGroup(agentGroup); + } + + // remove all current steps + queueGroup.getSteps().clear(); + + // set steps + OpenAcdRecipeStep step; + OpenAcdRecipeCondition condition; + OpenAcdRecipeAction action; + OpenAcdRecipeActionRestInfo actionRestInfo; + + List recipeStepsRestInfo = queueGroupRestInfo.getSteps(); + for (OpenAcdRecipeStepRestInfo recipeStepRestInfo : recipeStepsRestInfo) { + step = new OpenAcdRecipeStep(); + step.setFrequency(recipeStepRestInfo.getFrequency()); + + + // add conditions + step.getConditions().clear(); + for (OpenAcdRecipeConditionRestInfo recipeConditionRestInfo : recipeStepRestInfo.getConditions()) { + condition = new OpenAcdRecipeCondition(); + condition.setCondition(recipeConditionRestInfo.getCondition()); + condition.setRelation(recipeConditionRestInfo.getRelation()); + condition.setValueCondition(recipeConditionRestInfo.getValueCondition()); + + step.addCondition(condition); + } + + // add action + action = new OpenAcdRecipeAction(); + actionRestInfo = recipeStepRestInfo.getAction(); + action.setAction(actionRestInfo.getAction()); + action.setActionValue(actionRestInfo.getActionValue()); + + // set action skills (separate from skills assigned to queue + for (OpenAcdSkillRestInfo skillRestInfo : actionRestInfo.getSkills()) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + action.addSkill(skill); + } + + step.setAction(action); + + queueGroup.addStep(step); + } + } + + + // REST Representations + // -------------------- + + static class OpenAcdQueueGroupsRepresentation extends XStreamRepresentation { + + public OpenAcdQueueGroupsRepresentation(MediaType mediaType, OpenAcdQueueGroupsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdQueueGroupsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-queue-group", OpenAcdQueueGroupsBundleRestInfo.class); + xstream.alias("group", OpenAcdQueueGroupRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("agentGroup", OpenAcdAgentGroupRestInfo.class); + xstream.alias("step", OpenAcdRecipeStepRestInfo.class); + xstream.alias("condition", OpenAcdRecipeConditionRestInfo.class); + xstream.alias("action", OpenAcdRecipeActionRestInfo.class); + } + } + + static class OpenAcdQueueGroupRepresentation extends XStreamRepresentation { + + public OpenAcdQueueGroupRepresentation(MediaType mediaType, OpenAcdQueueGroupRestInfoFull object) { + super(mediaType, object); + } + + public OpenAcdQueueGroupRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", OpenAcdQueueGroupRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("agentGroup", OpenAcdAgentGroupRestInfo.class); + xstream.alias("step", OpenAcdRecipeStepRestInfo.class); + xstream.alias("condition", OpenAcdRecipeConditionRestInfo.class); + xstream.alias("action", OpenAcdRecipeActionRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdQueueGroupsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_groups; + + public OpenAcdQueueGroupsBundleRestInfo(List queueGroups, MetadataRestInfo metadata) { + m_metadata = metadata; + m_groups = queueGroups; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getGroups() { + return m_groups; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueuesResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueuesResource.java new file mode 100644 index 0000000000..04e2a510fc --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdQueuesResource.java @@ -0,0 +1,776 @@ +/* + * + * OpenAcdQueuesResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, I. Wesson, D. Waseem, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgentGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueue; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueueGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeAction; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeCondition; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeStep; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdAgentGroupRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdQueueRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeActionRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeConditionRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdRecipeStepRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdQueuesResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + enum SortField { + NAME, GROUPNAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Queue + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdQueueRestInfoFull queueRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + queueRestInfo = createQueueRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), ResponseCode.ERROR_READ_FAILED, "Read Queue failed", exception.getLocalizedMessage()); + } + + return new OpenAcdQueueRepresentation(variant.getMediaType(), queueRestInfo); + } + + + // if not single, process request for list + List queues = m_openAcdContext.getQueues(); + List queuesRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortQueues(queues); + + // set requested records and get resulting metadata + metadataRestInfo = addQueues(queuesRestInfo, queues); + + // create final restinfo + OpenAcdQueuesBundleRestInfo queuesBundleRestInfo = new OpenAcdQueuesBundleRestInfo(queuesRestInfo, metadataRestInfo); + + return new OpenAcdQueuesRepresentation(variant.getMediaType(), queuesBundleRestInfo); + } + + + // PUT - Update or Add single Skill + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdQueueRepresentation representation = new OpenAcdQueueRepresentation(entity); + OpenAcdQueueRestInfoFull queueRestInfo = representation.getObject(); + OpenAcdQueue queue; + + // validate input for update or create + ValidationInfo validationInfo = validate(queueRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + queue = m_openAcdContext.getQueueById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateQueue(queue, queueRestInfo); + m_openAcdContext.saveQueue(queue); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_WRITE_FAILED, "Update Queue failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), ResponseCode.SUCCESS_UPDATED, "Updated Queue", queue.getId()); + + return; + } + + + // otherwise add new + try { + queue = createQueue(queueRestInfo); + m_openAcdContext.saveQueue(queue); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), ResponseCode.ERROR_WRITE_FAILED, "Create Queue failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), ResponseCode.SUCCESS_CREATED, "Created Queue", queue.getId()); + } + + + // DELETE - Delete single Skill + // ---------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdQueue queue; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + queue = m_openAcdContext.getQueueById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteQueue(queue); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Queue", queue.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdQueueRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + List relation = Arrays.asList("is", "isNot"); + List condition1 = Arrays.asList("available_agents", "eligible_agents", "calls_queued", "queue_position", "hour", "weekday", "client_calls_queued"); + List condition2 = Arrays.asList("ticks", "client", "media_type", "caller_id", "caller_name"); + List equalityRelation = Arrays.asList("is", "greater", "less"); + List mediaValues = Arrays.asList("voice", "email", "voicemail", "chat"); + + String name = restInfo.getName(); + + // rest mods the hours with 24 to give a new hour, so allow over 23 + + for (int i = 0; i < name.length(); i++) { + if ((!Character.isLetterOrDigit(name.charAt(i)) && !(Character.getType(name.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && name.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Name' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + for (int i = 0; i < restInfo.getSteps().size(); i++) { + if (restInfo.getSteps().get(i).getAction().getAction().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action' cannot be empty and must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + + if (restInfo.getSteps().get(i).getAction().getAction().equals("announce") || restInfo.getSteps().get(i).getAction().getAction().equals("set_priority")) { + if (restInfo.getSteps().get(i).getAction().getActionValue().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action Value' cannot be empty and must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + for (int j = 0; j < m_openAcdContext.getClients().size(); j++) { + if (m_openAcdContext.getClients().get(j).getIdentity().equals(restInfo.getSteps().get(i).getAction().getActionValue())) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: Client Does not Exist"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getAction().getAction().equals("set_priority")) { + for (int j = 0; j < restInfo.getSteps().get(i).getAction().getActionValue().length(); j++) { + char c = restInfo.getSteps().get(i).getAction().getActionValue().charAt(j); + if (!Character.isDigit(c) && c != '*') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Action Value' must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + + for (int k = 0; k < restInfo.getSteps().get(i).getConditions().size(); k++) { + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().isEmpty() || restInfo.getSteps().get(i).getConditions().get(k).getRelation().isEmpty() || restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().isEmpty()) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Condtion' cannot be empty"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + + } + + if (condition2.contains(restInfo.getSteps().get(i).getConditions().get(k).getCondition())) { + if (!(relation.contains(restInfo.getSteps().get(i).getConditions().get(k).getRelation()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Relation' must only be 'is' or 'isNot'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + + } + + if (condition1.contains(restInfo.getSteps().get(i).getConditions().get(k).getCondition())) { + if (!(equalityRelation.contains(restInfo.getSteps().get(i).getConditions().get(k).getRelation()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Relation' must only be 'is', 'greater', or 'less'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("media_type")) { + if (!(mediaValues.contains(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()))) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' can only be 'voice', 'email', 'voicemail', or 'chat'"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("weekday")) { + if (Integer.parseInt(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()) < 1 || Integer.parseInt(restInfo.getSteps().get(i).getConditions().get(k).getValueCondition()) > 7) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' must be between 1 and 7"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + if (!(restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("caller_id") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("caller_name") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("media_type") || restInfo.getSteps().get(i).getConditions().get(k).getCondition().equals("client"))) { + for (int j = 0; j < restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().length(); j++) { + char c = restInfo.getSteps().get(i).getConditions().get(k).getValueCondition().charAt(j); + if (!Character.isDigit(c) && c != '*') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Value Condition' must only contain numbers and *"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + } + } + } + + return validationInfo; + } + + private OpenAcdQueueRestInfoFull createQueueRestInfo(int id) throws ResourceException { + OpenAcdQueueRestInfoFull queueRestInfo; + List skillsRestInfo; + List agentGroupsRestInfo; + List recipeStepRestInfo; + + OpenAcdQueue queue = m_openAcdContext.getQueueById(id); + skillsRestInfo = createSkillsRestInfo(queue.getSkills()); + agentGroupsRestInfo = createAgentGroupsRestInfo(queue); + recipeStepRestInfo = createRecipeStepsRestInfo(queue); + queueRestInfo = new OpenAcdQueueRestInfoFull(queue, skillsRestInfo, agentGroupsRestInfo, recipeStepRestInfo); + + return queueRestInfo; + } + + private List createRecipeStepsRestInfo(OpenAcdQueue queue) { + List recipeStepsRestInfo; + OpenAcdRecipeStepRestInfo recipeStepRestInfo; + + Set groupRecipeSteps = queue.getSteps(); + recipeStepsRestInfo = new ArrayList(groupRecipeSteps.size()); + + for (OpenAcdRecipeStep groupRecipeStep : groupRecipeSteps) { + recipeStepRestInfo = new OpenAcdRecipeStepRestInfo(groupRecipeStep, createRecipeActionRestInfo(groupRecipeStep), createRecipeConditionsRestInfo(groupRecipeStep)); + recipeStepsRestInfo.add(recipeStepRestInfo); + } + return recipeStepsRestInfo; + } + + private OpenAcdRecipeActionRestInfo createRecipeActionRestInfo(OpenAcdRecipeStep step) { + OpenAcdRecipeActionRestInfo recipeActionRestInfo; + List skillsRestInfo; + + // get skills associated with action + skillsRestInfo = createSkillsRestInfo(step.getAction().getSkills()); + recipeActionRestInfo = new OpenAcdRecipeActionRestInfo(step.getAction(), skillsRestInfo); + + return recipeActionRestInfo; + } + + private List createRecipeConditionsRestInfo(OpenAcdRecipeStep step) { + List recipeConditionsRestInfo; + OpenAcdRecipeConditionRestInfo recipeConditionRestInfo; + + + List groupRecipeConditions = step.getConditions(); + recipeConditionsRestInfo = new ArrayList(groupRecipeConditions.size()); + + for (OpenAcdRecipeCondition groupRecipeCondition : groupRecipeConditions) { + recipeConditionRestInfo = new OpenAcdRecipeConditionRestInfo(groupRecipeCondition); + recipeConditionsRestInfo.add(recipeConditionRestInfo); + } + + return recipeConditionsRestInfo; + } + + private List createSkillsRestInfo(Set skills) { + List skillsRestInfo; + OpenAcdSkillRestInfo skillRestInfo; + + // create list of skill restinfos from set of skills + skillsRestInfo = new ArrayList(skills.size()); + + for (OpenAcdSkill skill : skills) { + skillRestInfo = new OpenAcdSkillRestInfo(skill); + skillsRestInfo.add(skillRestInfo); + } + + return skillsRestInfo; + } + + private List createAgentGroupsRestInfo(OpenAcdQueue queue) { + List agentGroupsRestInfo; + OpenAcdAgentGroupRestInfo agentGroupRestInfo; + + // create list of agent group restinfos for single group + Set groupAgentGroups = queue.getAgentGroups(); + agentGroupsRestInfo = new ArrayList(groupAgentGroups.size()); + + for (OpenAcdAgentGroup groupAgentGroup : groupAgentGroups) { + agentGroupRestInfo = new OpenAcdAgentGroupRestInfo(groupAgentGroup); + agentGroupsRestInfo.add(agentGroupRestInfo); + } + + return agentGroupsRestInfo; + } + + private MetadataRestInfo addQueues(List queuesRestInfo, List queues) { + OpenAcdQueueRestInfoFull queueRestInfo; + List skillsRestInfo; + List agentGroupRestInfo; + List recipeStepRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, queues.size()); + + // create list of queue restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdQueue queue = queues.get(index); + + skillsRestInfo = createSkillsRestInfo(queue.getSkills()); + agentGroupRestInfo = createAgentGroupsRestInfo(queue); + recipeStepRestInfo = createRecipeStepsRestInfo(queue); + queueRestInfo = new OpenAcdQueueRestInfoFull(queue, skillsRestInfo, agentGroupRestInfo, recipeStepRestInfo); + queuesRestInfo.add(queueRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortQueues(List queues) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case GROUPNAME: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue1.getGroup().getName(), queue2.getGroup().getName()); + } + + }); + break; + + case NAME: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue1.getName(), queue2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue1.getDescription(), queue2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case GROUPNAME: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue2.getGroup().getName(), queue1.getGroup().getName()); + } + + }); + break; + + case NAME: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue2.getName(), queue1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(queues, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdQueue queue1 = (OpenAcdQueue) object1; + OpenAcdQueue queue2 = (OpenAcdQueue) object2; + return RestUtilities.compareIgnoreCaseNullSafe(queue2.getDescription(), queue1.getDescription()); + } + + }); + break; + } + } + } + + private void updateQueue(OpenAcdQueue queue, OpenAcdQueueRestInfoFull queueRestInfo) throws ResourceException { + String tempString; + OpenAcdQueueGroup queueGroup; + + // do not allow empty name + tempString = queueRestInfo.getName(); + if (!tempString.isEmpty()) { + queue.setName(tempString); + } + queueGroup = getQueueGroup(queueRestInfo); + + queue.setGroup(queueGroup); + queue.setDescription(queueRestInfo.getDescription()); + queue.setWeight(queueRestInfo.getWeight()); + + addLists(queue, queueRestInfo); + } + + private OpenAcdQueue createQueue(OpenAcdQueueRestInfoFull queueRestInfo) throws ResourceException { + OpenAcdQueue queue = new OpenAcdQueue(); + OpenAcdQueueGroup queueGroup; + + // copy fields from rest info + queue.setName(queueRestInfo.getName()); + queueGroup = getQueueGroup(queueRestInfo); + + queue.setGroup(queueGroup); + queue.setDescription(queueRestInfo.getDescription()); + queue.setWeight(queueRestInfo.getWeight()); + + addLists(queue, queueRestInfo); + + return queue; + } + + private void addLists(OpenAcdQueue queue, OpenAcdQueueRestInfoFull queueRestInfo) { + // remove all skills + queue.getSkills().clear(); + + // set skills + OpenAcdSkill skill; + List skillsRestInfo = queueRestInfo.getSkills(); + for (OpenAcdSkillRestInfo skillRestInfo : skillsRestInfo) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + queue.addSkill(skill); + } + + // remove all agent groups + queue.getAgentGroups().clear(); + + // set agent groups + OpenAcdAgentGroup agentGroup; + List agentGroupsRestInfo = queueRestInfo.getAgentGroups(); + for (OpenAcdAgentGroupRestInfo agentGroupRestInfo : agentGroupsRestInfo) { + agentGroup = m_openAcdContext.getAgentGroupById(agentGroupRestInfo.getId()); + queue.addAgentGroup(agentGroup); + } + + // remove all current steps + queue.getSteps().clear(); + + // set steps + OpenAcdRecipeStep step; + OpenAcdRecipeCondition condition; + OpenAcdRecipeAction action; + OpenAcdRecipeActionRestInfo actionRestInfo; + + List recipeStepsRestInfo = queueRestInfo.getSteps(); + for (OpenAcdRecipeStepRestInfo recipeStepRestInfo : recipeStepsRestInfo) { + step = new OpenAcdRecipeStep(); + step.setFrequency(recipeStepRestInfo.getFrequency()); + + + // add conditions + step.getConditions().clear(); + for (OpenAcdRecipeConditionRestInfo recipeConditionRestInfo : recipeStepRestInfo.getConditions()) { + condition = new OpenAcdRecipeCondition(); + condition.setCondition(recipeConditionRestInfo.getCondition()); + condition.setRelation(recipeConditionRestInfo.getRelation()); + condition.setValueCondition(recipeConditionRestInfo.getValueCondition()); + + step.addCondition(condition); + } + + // add action + action = new OpenAcdRecipeAction(); + actionRestInfo = recipeStepRestInfo.getAction(); + action.setAction(actionRestInfo.getAction()); + action.setActionValue(actionRestInfo.getActionValue()); + + // set action skills (separate from skills assigned to queue + for (OpenAcdSkillRestInfo skillRestInfo : actionRestInfo.getSkills()) { + skill = m_openAcdContext.getSkillById(skillRestInfo.getId()); + action.addSkill(skill); + } + + step.setAction(action); + + queue.addStep(step); + } + + } + + private OpenAcdQueueGroup getQueueGroup(OpenAcdQueueRestInfoFull queueRestInfo) throws ResourceException { + OpenAcdQueueGroup queueGroup; + int groupId = 0; + + try { + groupId = queueRestInfo.getGroupId(); + queueGroup = m_openAcdContext.getQueueGroupById(groupId); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "Queue Group ID " + groupId + " not found."); + } + + return queueGroup; + } + + + // REST Representations + // -------------------- + + static class OpenAcdQueuesRepresentation extends XStreamRepresentation { + + public OpenAcdQueuesRepresentation(MediaType mediaType, OpenAcdQueuesBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdQueuesRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-queue", OpenAcdQueuesBundleRestInfo.class); + xstream.alias("queue", OpenAcdQueueRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("agentGroup", OpenAcdAgentGroupRestInfo.class); + xstream.alias("step", OpenAcdRecipeStepRestInfo.class); + xstream.alias("condition", OpenAcdRecipeConditionRestInfo.class); + xstream.alias("action", OpenAcdRecipeActionRestInfo.class); + } + } + + static class OpenAcdQueueRepresentation extends XStreamRepresentation { + + public OpenAcdQueueRepresentation(MediaType mediaType, OpenAcdQueueRestInfoFull object) { + super(mediaType, object); + } + + public OpenAcdQueueRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("queue", OpenAcdQueueRestInfoFull.class); + xstream.alias("skill", OpenAcdSkillRestInfo.class); + xstream.alias("agentGroup", OpenAcdAgentGroupRestInfo.class); + xstream.alias("step", OpenAcdRecipeStepRestInfo.class); + xstream.alias("condition", OpenAcdRecipeConditionRestInfo.class); + xstream.alias("action", OpenAcdRecipeActionRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdQueuesBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_queues; + + public OpenAcdQueuesBundleRestInfo(List queues, MetadataRestInfo metadata) { + m_metadata = metadata; + m_queues = queues; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getQueues() { + return m_queues; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdReleaseCodesResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdReleaseCodesResource.java new file mode 100644 index 0000000000..441621654c --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdReleaseCodesResource.java @@ -0,0 +1,465 @@ +/* + * + * OpenAcdReleaseCodesResource.java - A Restlet to read Release Code data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Waseem + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdReleaseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdReleaseCodeRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdReleaseCodesResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + enum SortField { + LABEL, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Release Code + // ------------------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdReleaseCodeRestInfo releaseCodeRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + releaseCodeRestInfo = createReleaseCodeRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Release Code failed", exception.getLocalizedMessage()); + } + + return new OpenAcdReleaseCodeRepresentation(variant.getMediaType(), releaseCodeRestInfo); + } + + + // if not single, process request for list + List releaseCodes = m_openAcdContext.getReleaseCodes(); + List releaseCodesRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortReleaseCodes(releaseCodes); + + // set requested records and get resulting metadata + metadataRestInfo = addReleaseCodes(releaseCodesRestInfo, releaseCodes); + + // create final restinfo + OpenAcdReleaseCodesBundleRestInfo releaseCodesBundleRestInfo = new OpenAcdReleaseCodesBundleRestInfo(releaseCodesRestInfo, metadataRestInfo); + + return new OpenAcdReleaseCodesRepresentation(variant.getMediaType(), releaseCodesBundleRestInfo); + } + + + // PUT - Update or Add single Skill + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdReleaseCodeRepresentation representation = new OpenAcdReleaseCodeRepresentation(entity); + OpenAcdReleaseCodeRestInfo releaseCodeRestInfo = representation.getObject(); + OpenAcdReleaseCode releaseCode; + + // validate input for update or create + ValidationInfo validationInfo = validate(releaseCodeRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + releaseCode = m_openAcdContext.getReleaseCodeById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateReleaseCode(releaseCode, releaseCodeRestInfo); + m_openAcdContext.saveReleaseCode(releaseCode); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Release Code failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Release Code", releaseCode.getId()); + + return; + } + + + // otherwise add new + try { + releaseCode = createReleaseCode(releaseCodeRestInfo); + m_openAcdContext.saveReleaseCode(releaseCode); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Release Code failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Release Code", releaseCode.getId()); + } + + + // DELETE - Delete single Skill + // ---------------------------- + + // deleteReleaseCode() not available from openAcdContext + @Override + public void removeRepresentations() throws ResourceException { + // for some reason release codes are deleted by providing collection of ids, not by providing release code object + Collection releaseCodeIds = new HashSet(); + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + releaseCodeIds.add(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.removeReleaseCodes(releaseCodeIds); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdReleaseCodeRestInfo restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + // release code object will allow store of bias other value than -1, 0, or 1, + // but then current SipXconfig administrative UI will display nothing for bias name. + int bias = restInfo.getBias(); + if ((bias < -1) || (bias > 1)) { + validationInfo.valid = false; + validationInfo.message = "Validation Error: Bias must be be -1, 0 or 1"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + + return validationInfo; + } + + return validationInfo; + } + + private OpenAcdReleaseCodeRestInfo createReleaseCodeRestInfo(int id) throws ResourceException { + OpenAcdReleaseCodeRestInfo releaseCodeRestInfo; + + try { + OpenAcdReleaseCode releaseCode = m_openAcdContext.getReleaseCodeById(id); + releaseCodeRestInfo = new OpenAcdReleaseCodeRestInfo(releaseCode); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "ID " + id + " not found."); + } + + return releaseCodeRestInfo; + } + + private MetadataRestInfo addReleaseCodes(List releaseCodesRestInfo, List releaseCodes) { + OpenAcdReleaseCodeRestInfo releaseCodeRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, releaseCodes.size()); + + // create list of releaseCode restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdReleaseCode releaseCode = releaseCodes.get(index); + + releaseCodeRestInfo = new OpenAcdReleaseCodeRestInfo(releaseCode); + releaseCodesRestInfo.add(releaseCodeRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortReleaseCodes(List releaseCodes) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case LABEL: + Collections.sort(releaseCodes, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdReleaseCode releaseCode1 = (OpenAcdReleaseCode) object1; + OpenAcdReleaseCode releaseCode2 = (OpenAcdReleaseCode) object2; + return RestUtilities.compareIgnoreCaseNullSafe(releaseCode1.getLabel(), releaseCode2.getLabel()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(releaseCodes, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdReleaseCode releaseCode1 = (OpenAcdReleaseCode) object1; + OpenAcdReleaseCode releaseCode2 = (OpenAcdReleaseCode) object2; + return RestUtilities.compareIgnoreCaseNullSafe(releaseCode1.getDescription(), releaseCode2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case LABEL: + Collections.sort(releaseCodes, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdReleaseCode releaseCode1 = (OpenAcdReleaseCode) object1; + OpenAcdReleaseCode releaseCode2 = (OpenAcdReleaseCode) object2; + return RestUtilities.compareIgnoreCaseNullSafe(releaseCode2.getLabel(), releaseCode1.getLabel()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(releaseCodes, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdReleaseCode releaseCode1 = (OpenAcdReleaseCode) object1; + OpenAcdReleaseCode releaseCode2 = (OpenAcdReleaseCode) object2; + return RestUtilities.compareIgnoreCaseNullSafe(releaseCode2.getDescription(), releaseCode1.getDescription()); + } + + }); + break; + } + } + } + + private void updateReleaseCode(OpenAcdReleaseCode releaseCode, OpenAcdReleaseCodeRestInfo releaseCodeRestInfo) throws ResourceException { + String tempString; + + // do not allow empty name + tempString = releaseCodeRestInfo.getLabel(); + if (!tempString.isEmpty()) { + releaseCode.setName(tempString); + } + + releaseCode.setLabel(releaseCodeRestInfo.getLabel()); + releaseCode.setDescription(releaseCodeRestInfo.getDescription()); + releaseCode.setBias(releaseCodeRestInfo.getBias()); + } + + private OpenAcdReleaseCode createReleaseCode(OpenAcdReleaseCodeRestInfo releaseCodeRestInfo) throws ResourceException { + OpenAcdReleaseCode releaseCode = new OpenAcdReleaseCode(); + + // copy fields from rest info + releaseCode.setLabel(releaseCodeRestInfo.getLabel()); + releaseCode.setDescription(releaseCodeRestInfo.getDescription()); + releaseCode.setBias(releaseCodeRestInfo.getBias()); + + return releaseCode; + } + + + // REST Representations + // -------------------- + + static class OpenAcdReleaseCodesRepresentation extends XStreamRepresentation { + + public OpenAcdReleaseCodesRepresentation(MediaType mediaType, OpenAcdReleaseCodesBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdReleaseCodesRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-release-code", OpenAcdReleaseCodesBundleRestInfo.class); + xstream.alias("release-code", OpenAcdReleaseCodeRestInfo.class); + } + } + + static class OpenAcdReleaseCodeRepresentation extends XStreamRepresentation { + + public OpenAcdReleaseCodeRepresentation(MediaType mediaType, OpenAcdReleaseCodeRestInfo object) { + super(mediaType, object); + } + + public OpenAcdReleaseCodeRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("release-code", OpenAcdReleaseCodeRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdReleaseCodesBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_releaseCodes; + + public OpenAcdReleaseCodesBundleRestInfo(List releaseCodes, MetadataRestInfo metadata) { + m_metadata = metadata; + m_releaseCodes = releaseCodes; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getReleaseCodes() { + return m_releaseCodes; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSettingsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSettingsResource.java new file mode 100644 index 0000000000..c9191d7ae0 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSettingsResource.java @@ -0,0 +1,160 @@ +/* + * + * OpenAcdSettingsResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Waseem + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSettings; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdSettingsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + + // GET - Retrieve all and single Client + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // if not single, process request for list + OpenAcdSettings settings = m_openAcdContext.getSettings(); + OpenAcdSettingRestInfo settingsRestInfo = new OpenAcdSettingRestInfo(settings); + + return new OpenAcdSettingRepresentation(variant.getMediaType(), settingsRestInfo); + } + + + // PUT - Update single Setting + // --------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdSettingRepresentation representation = new OpenAcdSettingRepresentation(entity); + OpenAcdSettingRestInfo settingRestInfo = representation.getObject(); + OpenAcdSettings settings; + + // assign new setting + try { + settings = m_openAcdContext.getSettings(); + updateSettings(settings, settingRestInfo); + m_openAcdContext.saveSettings(settings); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Assign Setting failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Assigned Setting", settings.getId()); + } + + + // Helper functions + // ---------------- + + private void updateSettings(OpenAcdSettings settings, OpenAcdSettingRestInfo settingRestInfo) throws ResourceException { + settings.setSettingValue("openacd-config/log_level", settingRestInfo.getLogLevel()); + } + + + // REST Representations + // -------------------- + + static class OpenAcdSettingRepresentation extends XStreamRepresentation { + + public OpenAcdSettingRepresentation(MediaType mediaType, OpenAcdSettingRestInfo object) { + super(mediaType, object); + } + + public OpenAcdSettingRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("setting", OpenAcdSettingRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdSettingRestInfo { + private final int m_id; + private final String m_logLevel; + + public OpenAcdSettingRestInfo(OpenAcdSettings setting) { + m_id = setting.getId(); + m_logLevel = setting.getLogLevel(); + } + + public int getId() { + return m_id; + } + + public String getLogLevel() { + return m_logLevel; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillGroupsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillGroupsResource.java new file mode 100644 index 0000000000..d18a4f4c7a --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillGroupsResource.java @@ -0,0 +1,501 @@ +/* + * + * OpenAcdAgentGroupsResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkillGroup; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillGroupRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdSkillGroupsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, NUMBERSKILLS, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Skill + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdSkillGroupRestInfo skillGroupRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + skillGroupRestInfo = createSkillGroupRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Skill Group failed", exception.getLocalizedMessage()); + } + + return new OpenAcdSkillGroupRepresentation(variant.getMediaType(), skillGroupRestInfo); + } + + + // if not single, process request for list + List skillGroups = m_openAcdContext.getSkillGroups(); + List skillGroupsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortSkillGroups(skillGroups); + + // set requested records and get resulting metadata + metadataRestInfo = addSkillGroups(skillGroupsRestInfo, skillGroups); + + // create final restinfo + OpenAcdSkillGroupsBundleRestInfo skillGroupsBundleRestInfo = new OpenAcdSkillGroupsBundleRestInfo(skillGroupsRestInfo, metadataRestInfo); + + return new OpenAcdSkillGroupsRepresentation(variant.getMediaType(), skillGroupsBundleRestInfo); + } + + + // PUT - Update or Create single + // ----------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdSkillGroupRepresentation representation = new OpenAcdSkillGroupRepresentation(entity); + OpenAcdSkillGroupRestInfo skillGroupRestInfo = representation.getObject(); + OpenAcdSkillGroup skillGroup; + + // validate input for update or create + ValidationInfo validationInfo = validate(skillGroupRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + skillGroup = m_openAcdContext.getSkillGroupById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateSkillGroup(skillGroup, skillGroupRestInfo); + m_openAcdContext.saveSkillGroup(skillGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Skill Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Skill Group", skillGroup.getId()); + + return; + } + + + // otherwise add new + try { + skillGroup = createSkillGroup(skillGroupRestInfo); + m_openAcdContext.saveSkillGroup(skillGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Skill Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Skill Group", skillGroup.getId()); + } + + + // DELETE - Delete single + // ---------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdSkillGroupRestInfo skillGroupRestInfo; + List skills; + + // skill groups are deleted by providing collection of ids, not by providing skill group object + Collection skillGroupIds = new HashSet(); + int idInt; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + skillGroupIds.add(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // sipxconfig ui does not allow delete of group with existing skills + skills = m_openAcdContext.getSkills(); + for (OpenAcdSkill skill : skills) { + if (skill.getGroup().getId() == idInt) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Skill " + skill.getName() + " still refers to this group."); + return; + } + } + + m_openAcdContext.removeSkillGroups(skillGroupIds); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Skill Group", idInt); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdSkillGroupRestInfo restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + String name = restInfo.getName(); + + for (int i = 0; i < name.length(); i++) { + if ((!Character.isLetterOrDigit(name.charAt(i)) && !(Character.getType(name.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && name.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: Skill group name must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + return validationInfo; + } + + private OpenAcdSkillGroupRestInfo createSkillGroupRestInfo(int id) throws ResourceException { + OpenAcdSkillGroupRestInfo skillGroupRestInfo; + + try { + OpenAcdSkillGroup skillGroup = m_openAcdContext.getSkillGroupById(id); + skillGroupRestInfo = new OpenAcdSkillGroupRestInfo(skillGroup); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "ID " + id + " not found."); + } + + return skillGroupRestInfo; + } + + private MetadataRestInfo addSkillGroups(List skillsRestInfo, List skillGroups) { + OpenAcdSkillGroupRestInfo skillRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, skillGroups.size()); + + // create list of skill restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdSkillGroup skillGroup = skillGroups.get(index); + + skillRestInfo = new OpenAcdSkillGroupRestInfo(skillGroup); + skillsRestInfo.add(skillRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortSkillGroups(List skillGroups) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skillGroup1.getName(), skillGroup2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skillGroup1.getDescription(), skillGroup2.getDescription()); + } + + }); + break; + + case NUMBERSKILLS: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(String.valueOf(skillGroup1.getSkills().size()), String.valueOf(skillGroup2.getSkills().size())); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skillGroup2.getName(), skillGroup1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skillGroup2.getDescription(), skillGroup1.getDescription()); + } + + }); + break; + + case NUMBERSKILLS: + Collections.sort(skillGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkillGroup skillGroup1 = (OpenAcdSkillGroup) object1; + OpenAcdSkillGroup skillGroup2 = (OpenAcdSkillGroup) object2; + return RestUtilities.compareIgnoreCaseNullSafe(String.valueOf(skillGroup2.getSkills().size()), String.valueOf(skillGroup1.getSkills().size())); + } + + }); + break; + } + } + } + + private void updateSkillGroup(OpenAcdSkillGroup skillGroup, OpenAcdSkillGroupRestInfo skillGroupRestInfo) throws ResourceException { + String tempString; + + // do not allow empty name + tempString = skillGroupRestInfo.getName(); + if (!tempString.isEmpty()) { + skillGroup.setName(tempString); + } + + skillGroup.setDescription(skillGroupRestInfo.getDescription()); + } + + private OpenAcdSkillGroup createSkillGroup(OpenAcdSkillGroupRestInfo skillGroupRestInfo) throws ResourceException { + OpenAcdSkillGroup skillGroup = new OpenAcdSkillGroup(); + + // copy fields from rest info + skillGroup.setName(skillGroupRestInfo.getName()); + skillGroup.setDescription(skillGroupRestInfo.getDescription()); + + return skillGroup; + } + + + // REST Representations + // -------------------- + + static class OpenAcdSkillGroupsRepresentation extends XStreamRepresentation { + + public OpenAcdSkillGroupsRepresentation(MediaType mediaType, OpenAcdSkillGroupsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdSkillGroupsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-skill-group", OpenAcdSkillGroupsBundleRestInfo.class); + xstream.alias("group", OpenAcdSkillGroupRestInfo.class); + } + } + + static class OpenAcdSkillGroupRepresentation extends XStreamRepresentation { + + public OpenAcdSkillGroupRepresentation(MediaType mediaType, OpenAcdSkillGroupRestInfo object) { + super(mediaType, object); + } + + public OpenAcdSkillGroupRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", OpenAcdSkillGroupRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdSkillGroupsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_groups; + + public OpenAcdSkillGroupsBundleRestInfo(List skillGroups, MetadataRestInfo metadata) { + m_metadata = metadata; + m_groups = skillGroups; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getGroups() { + return m_groups; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillsResource.java new file mode 100644 index 0000000000..ee532028d3 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/OpenAcdSkillsResource.java @@ -0,0 +1,530 @@ +/* + * + * OpenAcdAgentGroupsResource.java - A Restlet to read Skill data from OpenACD within SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.openacd.OpenAcdContext; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkillGroup; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.OpenAcdSkillRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ResponseCode; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class OpenAcdSkillsResource extends UserResource { + + private OpenAcdContext m_openAcdContext; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, ATOM, GROUPNAME, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Skill + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + OpenAcdSkillRestInfoFull skillRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + skillRestInfo = createSkillRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read Skills failed", exception.getLocalizedMessage()); + } + + return new OpenAcdSkillRepresentation(variant.getMediaType(), skillRestInfo); + } + + + // if not single, process request for all + List skills = m_openAcdContext.getSkills(); + List skillsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortSkills(skills); + + // set requested agents groups and get resulting metadata + metadataRestInfo = addSkills(skillsRestInfo, skills); + + // create final restinfo + OpenAcdSkillsBundleRestInfo skillsBundleRestInfo = new OpenAcdSkillsBundleRestInfo(skillsRestInfo, metadataRestInfo); + + return new OpenAcdSkillsRepresentation(variant.getMediaType(), skillsBundleRestInfo); + } + + + // PUT - Update or Add single Skill + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + OpenAcdSkillRepresentation representation = new OpenAcdSkillRepresentation(entity); + OpenAcdSkillRestInfoFull skillRestInfo = representation.getObject(); + OpenAcdSkill skill = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(skillRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + skill = m_openAcdContext.getSkillById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateSkill(skill, skillRestInfo); + m_openAcdContext.saveSkill(skill); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Skill failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Skill", skill.getId()); + + return; + } + + + // otherwise add new + try { + skill = createSkill(skillRestInfo); + m_openAcdContext.saveSkill(skill); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Skill failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Skill", skill.getId()); + } + + + // DELETE - Delete single Skill + // ---------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + OpenAcdSkill skill; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + skill = m_openAcdContext.getSkillById(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + m_openAcdContext.deleteSkill(skill); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Skill", skill.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(OpenAcdSkillRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + String name = restInfo.getName(); + String atom = restInfo.getAtom(); + + for (int i = 0; i < name.length(); i++) { + if ((!Character.isLetterOrDigit(name.charAt(i)) && !(Character.getType(name.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && name.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: Skill Group 'Name' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + for (int i = 0; i < atom.length(); i++) { + if ((!Character.isLetterOrDigit(atom.charAt(i)) && !(Character.getType(atom.charAt(i)) == Character.CONNECTOR_PUNCTUATION)) && atom.charAt(i) != '-') { + validationInfo.valid = false; + validationInfo.message = "Validation Error: 'Atom' must only contain letters, numbers, dashes, and underscores"; + validationInfo.responseCode = ResponseCode.ERROR_BAD_INPUT; + } + } + + return validationInfo; + } + + private OpenAcdSkillRestInfoFull createSkillRestInfo(int id) throws ResourceException { + OpenAcdSkillRestInfoFull skillRestInfo = null; + + OpenAcdSkill skill = m_openAcdContext.getSkillById(id); + skillRestInfo = new OpenAcdSkillRestInfoFull(skill); + + return skillRestInfo; + } + + private MetadataRestInfo addSkills(List skillsRestInfo, List skills) { + OpenAcdSkillRestInfoFull skillRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, skills.size()); + + // create list of skill restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + OpenAcdSkill skill = skills.get(index); + + skillRestInfo = new OpenAcdSkillRestInfoFull(skill); + skillsRestInfo.add(skillRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortSkills(List skills) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill1.getName(), skill2.getName()); + } + + }); + break; + + case GROUPNAME: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill1.getGroupName(), skill2.getGroupName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill1.getDescription(), skill2.getDescription()); + } + + }); + break; + + + case ATOM: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill1.getAtom(), skill2.getAtom()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill2.getName(), skill1.getName()); + } + + }); + break; + + case GROUPNAME: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill2.getGroupName(), skill1.getGroupName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill2.getDescription(), skill1.getDescription()); + } + + }); + break; + + case ATOM: + Collections.sort(skills, new Comparator() { + + public int compare(Object object1, Object object2) { + OpenAcdSkill skill1 = (OpenAcdSkill) object1; + OpenAcdSkill skill2 = (OpenAcdSkill) object2; + return RestUtilities.compareIgnoreCaseNullSafe(skill2.getAtom(), skill1.getAtom()); + } + + }); + break; + } + } + } + + private void updateSkill(OpenAcdSkill skill, OpenAcdSkillRestInfoFull skillRestInfo) { + OpenAcdSkillGroup skillGroup; + String tempString; + + // do not allow empty name + tempString = skillRestInfo.getName(); + if (!tempString.isEmpty()) { + skill.setName(tempString); + } + + skill.setDescription(skillRestInfo.getDescription()); + + skillGroup = getSkillGroup(skillRestInfo); + skill.setGroup(skillGroup); + } + + private OpenAcdSkill createSkill(OpenAcdSkillRestInfoFull skillRestInfo) throws ResourceException { + OpenAcdSkillGroup skillGroup; + OpenAcdSkill skill = new OpenAcdSkill(); + + // copy fields from rest info + skill.setName(skillRestInfo.getName()); + skill.setDescription(skillRestInfo.getDescription()); + skill.setAtom(skillRestInfo.getAtom()); + + skillGroup = getSkillGroup(skillRestInfo); + skill.setGroup(skillGroup); + + return skill; + } + + private OpenAcdSkillGroup getSkillGroup(OpenAcdSkillRestInfoFull skillRestInfo) { + OpenAcdSkillGroup skillGroup; + int groupId = skillRestInfo.getGroupId(); + skillGroup = m_openAcdContext.getSkillGroupById(groupId); + + return skillGroup; + } + + + // REST Representations + // -------------------- + + static class OpenAcdSkillsRepresentation extends XStreamRepresentation { + + public OpenAcdSkillsRepresentation(MediaType mediaType, OpenAcdSkillsBundleRestInfo object) { + super(mediaType, object); + } + + public OpenAcdSkillsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("openacd-skill", OpenAcdSkillsBundleRestInfo.class); + xstream.alias("skill", OpenAcdSkillRestInfoFull.class); + } + } + + static class OpenAcdSkillRepresentation extends XStreamRepresentation { + + public OpenAcdSkillRepresentation(MediaType mediaType, OpenAcdSkillRestInfoFull object) { + super(mediaType, object); + } + + public OpenAcdSkillRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("skill", OpenAcdSkillRestInfoFull.class); + } + } + + + // REST info objects + // ----------------- + + static class OpenAcdSkillsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_skills; + + public OpenAcdSkillsBundleRestInfo(List skills, MetadataRestInfo metadata) { + m_metadata = metadata; + m_skills = skills; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getSkills() { + return m_skills; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setOpenAcdContext(OpenAcdContext openAcdContext) { + m_openAcdContext = openAcdContext; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/PermissionsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/PermissionsResource.java new file mode 100644 index 0000000000..cf40a8077c --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/PermissionsResource.java @@ -0,0 +1,485 @@ +/* + * + * PermissionsResource.java - A Restlet to read Skill data from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.permission.Permission; +import org.sipfoundry.sipxconfig.permission.PermissionManager; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PermissionRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class PermissionsResource extends UserResource { + + private PermissionManager m_permissionManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, LABEL, DEFAULTVALUE, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Skill + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + // Permissions do not use Id, so must key off Name + PermissionRestInfoFull permissionRestInfo = null; + String nameString = (String) getRequest().getAttributes().get("name"); + + if (nameString != null) { + try { + permissionRestInfo = createPermissionRestInfo(nameString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read permissions failed", exception.getLocalizedMessage()); + } + + return new PermissionRepresentation(variant.getMediaType(), permissionRestInfo); + } + + + // if not single, process request for all + List permissions = new ArrayList(m_permissionManager.getPermissions()); + List permissionsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort groups if specified + sortPermissions(permissions); + + // set requested agents groups and get resulting metadata + metadataRestInfo = addPermissions(permissionsRestInfo, permissions); + + // create final restinfo + PermissionsBundleRestInfo permissionsBundleRestInfo = new PermissionsBundleRestInfo(permissionsRestInfo, metadataRestInfo); + + return new PermissionsRepresentation(variant.getMediaType(), permissionsBundleRestInfo); + } + + + // PUT - Update or Add single Skill + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + PermissionRepresentation representation = new PermissionRepresentation(entity); + PermissionRestInfoFull permissionRestInfo = representation.getObject(); + Permission permission = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(permissionRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String nameString = (String) getRequest().getAttributes().get("name"); + + if (nameString != null) { + try { + permission = m_permissionManager.getPermissionByName(nameString); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Name " + nameString + " not found."); + return; + } + + // copy values over to existing + try { + updatePermission(permission, permissionRestInfo); + m_permissionManager.saveCallPermission(permission); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update Permission failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated Permission", permission.getName()); + + return; + } + + + // otherwise add new + try { + permission = createPermission(permissionRestInfo); + m_permissionManager.saveCallPermission(permission); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create Permission failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created Permission", permission.getName()); + } + + + // DELETE - Delete single Skill + // ---------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + Permission permission; + + // get id then delete single + String nameString = (String) getRequest().getAttributes().get("name"); + + if (nameString != null) { + try { + permission = m_permissionManager.getPermissionByName(nameString); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Name " + nameString + " not found."); + return; + } + + m_permissionManager.deleteCallPermission(permission); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted Permission", permission.getName()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "Name value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(PermissionRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private PermissionRestInfoFull createPermissionRestInfo(String name) throws ResourceException { + PermissionRestInfoFull permissionRestInfo = null; + + Permission permission = m_permissionManager.getPermissionByName(name); + permissionRestInfo = new PermissionRestInfoFull(permission); + + return permissionRestInfo; + } + + private MetadataRestInfo addPermissions(List permissionsRestInfo, List permissions) { + PermissionRestInfoFull permissionRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, permissions.size()); + + // create list of restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + Permission permission = permissions.get(index); + + permissionRestInfo = new PermissionRestInfoFull(permission); + permissionsRestInfo.add(permissionRestInfo); + } + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortPermissions(List permissions) { + // sort if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case LABEL: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission1.getLabel(),permission2.getLabel()); + } + + }); + break; + + case DEFAULTVALUE: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(Boolean.toString(permission1.getDefaultValue()),Boolean.toString(permission2.getDefaultValue())); + } + + }); + break; + + case NAME: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission1.getName(), permission2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission1.getDescription(), permission2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case LABEL: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission2.getLabel(),permission1.getLabel()); + } + + }); + break; + + case DEFAULTVALUE: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(Boolean.toString(permission2.getDefaultValue()),Boolean.toString(permission1.getDefaultValue())); + } + + }); + break; + + case NAME: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission2.getName(), permission1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(permissions, new Comparator() { + + public int compare(Object object1, Object object2) { + Permission permission1 = (Permission) object1; + Permission permission2 = (Permission) object2; + return RestUtilities.compareIgnoreCaseNullSafe(permission2.getDescription(), permission1.getDescription()); + } + + }); + break; + } + } + } + + private void updatePermission(Permission permission, PermissionRestInfoFull permissionRestInfo) { + String tempString; + + // do not allow empty label + tempString = permissionRestInfo.getLabel(); + if (!tempString.isEmpty()) { + permission.setLabel(tempString); + } + + permission.setDescription(permissionRestInfo.getDescription()); + permission.setDefaultValue(permissionRestInfo.getDefaultValue()); + } + + private Permission createPermission(PermissionRestInfoFull permissionRestInfo) throws ResourceException { + Permission permission = new Permission(); + + // copy fields from rest info + permission.setLabel(permissionRestInfo.getLabel()); + permission.setDescription(permissionRestInfo.getDescription()); + permission.setDefaultValue(permissionRestInfo.getDefaultValue()); + + // only available is custom call types + permission.setType(Permission.Type.CALL); + + return permission; + } + + // REST Representations + // -------------------- + + static class PermissionsRepresentation extends XStreamRepresentation { + + public PermissionsRepresentation(MediaType mediaType, PermissionsBundleRestInfo object) { + super(mediaType, object); + } + + public PermissionsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("permissions", PermissionsBundleRestInfo.class); + xstream.alias("permission", PermissionRestInfoFull.class); + } + } + + static class PermissionRepresentation extends XStreamRepresentation { + + public PermissionRepresentation(MediaType mediaType, PermissionRestInfoFull object) { + super(mediaType, object); + } + + public PermissionRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("permission", PermissionRestInfoFull.class); + } + } + + + // REST info objects + // ----------------- + + static class PermissionsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_permissions; + + public PermissionsBundleRestInfo(List permissions, MetadataRestInfo metadata) { + m_metadata = metadata; + m_permissions = permissions; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getPermissions() { + return m_permissions; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setPermissionManager(PermissionManager permissionManager) { + m_permissionManager = permissionManager; + } +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/RestUtilities.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/RestUtilities.java new file mode 100644 index 0000000000..4a699872df --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/RestUtilities.java @@ -0,0 +1,1135 @@ +/* + * +l * OpenAcdUtilities.java - Support functionality for OpenAcd Restlets + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import java.io.IOException; +import java.util.List; + +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.DomRepresentation; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.sipfoundry.sipxconfig.branch.Branch; +import org.sipfoundry.sipxconfig.common.User; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgent; +import org.sipfoundry.sipxconfig.openacd.OpenAcdAgentGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdClient; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueue; +import org.sipfoundry.sipxconfig.openacd.OpenAcdQueueGroup; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeAction; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeCondition; +import org.sipfoundry.sipxconfig.openacd.OpenAcdRecipeStep; +import org.sipfoundry.sipxconfig.openacd.OpenAcdReleaseCode; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkill; +import org.sipfoundry.sipxconfig.openacd.OpenAcdSkillGroup; +import org.sipfoundry.sipxconfig.permission.Permission; +import org.sipfoundry.sipxconfig.phonebook.Address; +import org.sipfoundry.sipxconfig.setting.Group; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class RestUtilities { + + public static int getIntFromAttribute(String attributeString) throws ResourceException { + int intFromAttribute; + + // attempt to parse attribute provided as an id + try { + intFromAttribute = Integer.parseInt(attributeString); + } + catch (Exception exception) { + throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Attribute " + attributeString + " invalid."); + } + + return intFromAttribute; + } + + public static PaginationInfo calculatePagination(Form form, int totalResults) { + PaginationInfo paginationInfo = new PaginationInfo(); + paginationInfo.totalResults = totalResults; + + // must specify both PageNumber and ResultsPerPage together + String pageNumberString = form.getFirstValue("page"); + String resultsPerPageString = form.getFirstValue("pagesize"); + + // attempt to parse pagination values from request + try { + paginationInfo.pageNumber = Integer.parseInt(pageNumberString); + paginationInfo.resultsPerPage = Integer.parseInt(resultsPerPageString); + } + catch (Exception exception) { + // default 0 for nothing + paginationInfo.pageNumber = 0; + paginationInfo.resultsPerPage = 0; + } + + // check for outrageous values or lack of parameters + if ((paginationInfo.pageNumber < 1) || (paginationInfo.resultsPerPage < 1)) { + paginationInfo.pageNumber = 0; + paginationInfo.resultsPerPage = 0; + paginationInfo.paginate = false; + } + else { + paginationInfo.paginate = true; + } + + + // do we have to paginate? + if (paginationInfo.paginate) { + paginationInfo.totalPages = ((paginationInfo.totalResults - 1) / paginationInfo.resultsPerPage) + 1; + + // check if only one page + // if (resultsPerPage >= totalResults) { + if (paginationInfo.totalPages == 1) { + paginationInfo.startIndex = 0; + paginationInfo.endIndex = paginationInfo.totalResults - 1; + paginationInfo.pageNumber = 1; + // design decision: should the resultsPerPage actually be set to totalResults? + // since totalResults are already available preserve call value + } + else { + // check if specified page number is on or beyoned last page (then use last page) + if (paginationInfo.pageNumber >= paginationInfo.totalPages) { + paginationInfo.pageNumber = paginationInfo.totalPages; + paginationInfo.startIndex = (paginationInfo.totalPages - 1) * paginationInfo.resultsPerPage; + paginationInfo.endIndex = paginationInfo.totalResults - 1; + } + else { + paginationInfo.startIndex = (paginationInfo.pageNumber - 1) * paginationInfo.resultsPerPage; + paginationInfo.endIndex = paginationInfo.startIndex + paginationInfo.resultsPerPage - 1; + } + } + } + else { + // default values assuming no pagination + paginationInfo.startIndex = 0; + paginationInfo.endIndex = paginationInfo.totalResults - 1; + paginationInfo.pageNumber = 1; + paginationInfo.totalPages = 1; + paginationInfo.resultsPerPage = paginationInfo.totalResults; + } + + return paginationInfo; + } + + public static SortInfo calculateSorting(Form form) { + SortInfo sortInfo = new SortInfo(); + + String sortDirectionString = form.getFirstValue("sortdir"); + String sortFieldString = form.getFirstValue("sortby"); + + // check for invalid input + if ((sortDirectionString == null) || (sortFieldString == null)) { + sortInfo.sort = false; + return sortInfo; + } + + if ((sortDirectionString.isEmpty()) || (sortFieldString.isEmpty())) { + sortInfo.sort = false; + return sortInfo; + } + + sortInfo.sort = true; + + // assume forward if get anything else but "reverse" + if (sortDirectionString.toLowerCase().equals("reverse")) { + sortInfo.directionForward = false; + } + else { + sortInfo.directionForward = true; + } + + // tough to type-check this one + sortInfo.sortField = sortFieldString; + + return sortInfo; + } + + public static int compareIgnoreCaseNullSafe (String left, String right) + { + if (left == null) + left = ""; + if (right == null) + right = ""; + + return left.compareToIgnoreCase(right); + } + + + // XML Response functions + // ---------------------- + + public static void setResponse(Response response, ResponseCode code, String message) { + try { + DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML); + Document doc = representation.getDocument(); + + // set response status + setResponseStatus(response, code); + + // create root node + Element elementResponse = doc.createElement("response"); + doc.appendChild(elementResponse); + + setResponseHeader(doc, elementResponse, code, message); + + // no related data (create function overloads to modify) + + response.setEntity(new DomRepresentation(MediaType.TEXT_XML, doc)); + + } + catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void setResponse(Response response, ResponseCode code, String message, int id) { + try { + DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML); + Document doc = representation.getDocument(); + + // set response status + setResponseStatus(response, code); + + // create root node + Element elementResponse = doc.createElement("response"); + doc.appendChild(elementResponse); + + setResponseHeader(doc, elementResponse, code, message); + + // add related data + Element elementData = doc.createElement("data"); + Element elementId = doc.createElement("id"); + elementId.appendChild(doc.createTextNode(String.valueOf(id))); + elementData.appendChild(elementId); + elementResponse.appendChild(elementData); + + response.setEntity(new DomRepresentation(MediaType.TEXT_XML, doc)); + + } + catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void setResponse(Response response, ResponseCode code, String message, String id) { + try { + DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML); + Document doc = representation.getDocument(); + + // set response status + setResponseStatus(response, code); + + // create root node + Element elementResponse = doc.createElement("response"); + doc.appendChild(elementResponse); + + setResponseHeader(doc, elementResponse, code, message); + + // add related data + Element elementData = doc.createElement("data"); + Element elementId = doc.createElement("id"); + elementId.appendChild(doc.createTextNode(id)); + elementData.appendChild(elementId); + elementResponse.appendChild(elementData); + + response.setEntity(new DomRepresentation(MediaType.TEXT_XML, doc)); + + } + catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void setResponseError(Response response, ResponseCode code, String message) { + Representation representation = getResponseError(response, code, message); + + response.setEntity(representation); + } + + public static Representation getResponseError(Response response, ResponseCode code, String message) { + try { + DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML); + Document doc = representation.getDocument(); + + // set response status + setResponseStatus(response, code); + + // create root node + Element elementResponse = doc.createElement("response"); + doc.appendChild(elementResponse); + + setResponseHeader(doc, elementResponse, code, message); + + return representation; // new DomRepresentation(MediaType.TEXT_XML, doc); + + } + catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } + + public static void setResponseError(Response response, ResponseCode code, String message, String additionalMessage) { + Representation representation = getResponseError(response, code, message, additionalMessage); + + response.setEntity(representation); + } + + public static Representation getResponseError(Response response, ResponseCode code, String message, String additionalMessage) { + try { + DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML); + Document doc = representation.getDocument(); + + // set response status + setResponseStatus(response, code); + + // create root node + Element elementResponse = doc.createElement("response"); + doc.appendChild(elementResponse); + + setResponseHeader(doc, elementResponse, code, message); + + // add related data + Element elementData = doc.createElement("data"); + Element elementId = doc.createElement("additionalMessage"); + elementId.appendChild(doc.createTextNode(additionalMessage)); + elementData.appendChild(elementId); + elementResponse.appendChild(elementData); + + return representation; // new DomRepresentation(MediaType.TEXT_XML, doc); + + } + catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } + + private static void setResponseHeader(Document doc, Element elementResponse, ResponseCode code, String message) { + + // add standard elements + Element elementCode = doc.createElement("code"); + elementCode.appendChild(doc.createTextNode(code.toString())); + elementResponse.appendChild(elementCode); + + Element elementMessage = doc.createElement("message"); + elementMessage.appendChild(doc.createTextNode(message)); + elementResponse.appendChild(elementMessage); + } + + private static void setResponseStatus(Response response, ResponseCode code) { + // set response status based on code + switch (code) { + case SUCCESS_CREATED: + response.setStatus(Status.SUCCESS_CREATED); + break; + + case ERROR_MISSING_INPUT: + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + break; + + case ERROR_BAD_INPUT: + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + break; + + case ERROR_WRITE_FAILED: + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + break; + + case ERROR_READ_FAILED: + response.setStatus(Status.SERVER_ERROR_INTERNAL); + break; + + default: + response.setStatus(Status.SUCCESS_OK); + } + } + + public static enum ResponseCode { + SUCCESS, SUCCESS_CREATED, SUCCESS_UPDATED, SUCCESS_DELETED, ERROR_MISSING_INPUT, ERROR_BAD_INPUT, ERROR_WRITE_FAILED, ERROR_READ_FAILED + } + + + // Data objects + // ------------ + + public static class PaginationInfo { + Boolean paginate = false; + int pageNumber = 0; + int resultsPerPage = 0; + int totalPages = 0; + int totalResults = 0; + int startIndex = 0; + int endIndex = 0; + } + + public static class SortInfo { + Boolean sort = false; + Boolean directionForward = true; + String sortField = ""; + } + + public static class ValidationInfo { + Boolean valid = true; + String message = "Valid"; + ResponseCode responseCode = ResponseCode.SUCCESS; + } + + + // Common Rest Info objects + // ------------------------ + + static class PermissionRestInfoFull { + private final String m_name; + private final String m_label; + private final String m_description; + private final boolean m_defaultValue; + private final Permission.Type m_type; + private final boolean m_builtIn; + + public PermissionRestInfoFull(Permission permission) { + m_name = permission.getName(); + m_label = permission.getLabel(); + m_description = permission.getDescription(); + m_defaultValue = permission.getDefaultValue(); + m_type = permission.getType(); + m_builtIn = permission.isBuiltIn(); + } + + public String getName() { + return m_name; + } + + public String getLabel() { + return m_label; + } + + public String getDescription() { + return m_description; + } + + public boolean getDefaultValue() { + return m_defaultValue; + } + + public Permission.Type getType() { + return m_type; + } + + public boolean getBuiltIn() { + return m_builtIn; + } + } + + static class BranchRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + + public BranchRestInfo(Branch branch) { + m_id = branch.getId(); + m_name = branch.getName(); + m_description = branch.getDescription(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + } + + static class BranchRestInfoFull extends BranchRestInfo { + private final Address m_address; + private final String m_phoneNumber; + private final String m_faxNumber; + + public BranchRestInfoFull(Branch branch) { + super(branch); + + m_address = branch.getAddress(); + m_phoneNumber = branch.getPhoneNumber(); + m_faxNumber = branch.getFaxNumber(); + } + + public Address getAddress() { + return m_address; + } + + public String getPhoneNumber() { + return m_phoneNumber; + } + + public String getFaxNumber() { + return m_faxNumber; + } + } + + static class UserGroupRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + + public UserGroupRestInfo(Group userGroup) { + m_id = userGroup.getId(); + m_name = userGroup.getName(); + m_description = userGroup.getDescription(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + } + + static class UserGroupRestInfoFull extends UserGroupRestInfo { + private final BranchRestInfoFull m_branch; + + public UserGroupRestInfoFull(Group userGroup, BranchRestInfoFull branchRestInfo) { + super(userGroup); + + m_branch = branchRestInfo; + } + + public BranchRestInfoFull getBranch() { + return m_branch; + } + } + + static class SettingBooleanRestInfo { + private final String m_name; + private final String m_value; + private final boolean m_defaultValue; + + public SettingBooleanRestInfo(String name, String value, boolean defaultValue) { + m_name = name; + m_value = value; + m_defaultValue = defaultValue; + } + + public String getName() { + return m_name; + } + + public String getValue() { + return m_value; + } + + public boolean getDefaultValue() { + return m_defaultValue; + } + } + + static class UserGroupPermissionRestInfoFull extends UserGroupRestInfo { + private final List m_permissions; + + public UserGroupPermissionRestInfoFull(Group userGroup, List settingsRestInfo) { + super(userGroup); + + m_permissions = settingsRestInfo; + } + + public List getPermissions() { + return m_permissions; + } + } + + static class UserRestInfoFull { + private final int m_id; + private final String m_userName; // also called "User ID" in gui + private final String m_lastName; + private final String m_firstName; + private final String m_pin; + private final String m_sipPassword; + private final String m_emailAddress; // this is actually from "Contact Information" tab. Maybe create separate API later + private final List m_groups; + private final BranchRestInfo m_branch; + private final List m_aliases; + + + public UserRestInfoFull(User user, List userGroupsRestInfo, BranchRestInfo branchRestInfo, List aliasesRestInfo) { + m_id = user.getId(); + m_userName = user.getUserName(); + m_lastName = user.getLastName(); + m_firstName = user.getFirstName(); + m_pin = ""; // pin is hardcoded to never display but must still be submitted + m_sipPassword = user.getSipPassword(); + m_emailAddress = user.getEmailAddress(); + m_groups = userGroupsRestInfo; + m_branch = branchRestInfo; + m_aliases = aliasesRestInfo; + } + + public int getId() { + return m_id; + } + + public String getUserName() { + return m_userName; + } + + public String getLastName() { + return m_lastName; + } + + public String getFirstName() { + return m_firstName; + } + + public String getPin() { + return m_pin; + } + + public String getSipPassword() { + return m_sipPassword; + } + + public String getEmailAddress() { + return m_emailAddress; + } + public List getGroups() { + return m_groups; + } + + public BranchRestInfo getBranch() { + return m_branch; + } + + public List getAliases() { + return m_aliases; + } + } + + static class AliasRestInfo { + + private final String m_alias; + + public AliasRestInfo(String alias) { + m_alias = alias; + } + + public String getAlias() { + return m_alias; + } + } + + + // Common OpenACD Rest Info objects + // ------------------------ + + static class MetadataRestInfo { + private final int m_totalResults; + private final int m_currentPage; + private final int m_totalPages; + private final int m_resultsPerPage; + + public MetadataRestInfo(PaginationInfo paginationInfo) { + m_totalResults = paginationInfo.totalResults; + m_currentPage = paginationInfo.pageNumber; + m_totalPages = paginationInfo.totalPages; + m_resultsPerPage = paginationInfo.resultsPerPage; + } + + public int getTotalResults() { + return m_totalResults; + } + + public int getCurrentPage() { + return m_currentPage; + } + + public int getTotalPages() { + return m_totalPages; + } + + public int getResultsPerPage() { + return m_resultsPerPage; + } + } + + static class OpenAcdSkillRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + private final String m_groupName; + + public OpenAcdSkillRestInfo(OpenAcdSkill skill) { + m_id = skill.getId(); + m_name = skill.getName(); + m_description = skill.getDescription(); + m_groupName = skill.getGroupName(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + + public String getGroupName() { + return m_groupName; + } + } + + static class OpenAcdSkillRestInfoFull extends OpenAcdSkillRestInfo { + private final String m_atom; + private final int m_groupId; + + public OpenAcdSkillRestInfoFull(OpenAcdSkill skill) { + super(skill); + m_atom = skill.getAtom(); + m_groupId = skill.getGroup().getId(); + } + + public String getAtom() { + return m_atom; + } + + public int getGroupId() { + return m_groupId; + } + } + + static class OpenAcdSkillGroupRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + + public OpenAcdSkillGroupRestInfo(OpenAcdSkillGroup skillGroup) { + m_id = skillGroup.getId(); + m_name = skillGroup.getName(); + m_description = skillGroup.getDescription(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + } + + static class OpenAcdQueueRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + private final String m_groupName; + + public OpenAcdQueueRestInfo(OpenAcdQueue queue) { + m_id = queue.getId(); + m_name = queue.getName(); + m_description = queue.getDescription(); + m_groupName = queue.getQueueGroup(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + + public String getGroupName() { + return m_groupName; + } + } + + static class OpenAcdQueueRestInfoFull extends OpenAcdQueueRestInfo { + private final int m_groupId; + private final int m_weight; + private final List m_skills; + private final List m_agentGroups; + private final List m_steps; + + public OpenAcdQueueRestInfoFull(OpenAcdQueue queue, List skills, List agentGroups, List steps) { + super(queue); + m_groupId = queue.getGroup().getId(); + m_weight = queue.getWeight(); + m_skills = skills; + m_agentGroups = agentGroups; + m_steps = steps; + } + + public int getGroupId() { + return m_groupId; + } + + public int getWeight() { + return m_weight; + } + + public List getSkills() { + return m_skills; + } + + public List getAgentGroups() { + return m_agentGroups; + } + + public List getSteps() { + return m_steps; + } + } + + + static class OpenAcdQueueGroupRestInfoFull { + private final String m_name; + private final int m_id; + private final String m_description; + private final List m_skills; + private final List m_agentGroups; + private final List m_steps; + + public OpenAcdQueueGroupRestInfoFull(OpenAcdQueueGroup queueGroup, List skills, List agentGroups, List steps) { + m_name = queueGroup.getName(); + m_id = queueGroup.getId(); + m_description = queueGroup.getDescription(); + m_skills = skills; + m_agentGroups = agentGroups; + m_steps = steps; + } + + public String getName() { + return m_name; + } + + public int getId() { + return m_id; + } + + public String getDescription() { + return m_description; + } + + public List getSkills() { + return m_skills; + } + + public List getAgentGroups() { + return m_agentGroups; + } + + public List getSteps() { + return m_steps; + } + } + + static class OpenAcdRecipeActionRestInfo { + private final String m_action; + private final String m_actionValue; + private final List m_skills; + + public OpenAcdRecipeActionRestInfo(OpenAcdRecipeAction action, List skills) { + m_action = action.getAction(); + m_actionValue = action.getActionValue(); + m_skills = skills; + } + + public String getAction() { + return m_action; + } + + public String getActionValue() { + return m_actionValue; + } + + public List getSkills() { + return m_skills; + } + } + + static class OpenAcdRecipeStepRestInfo { + private final int m_id; + private final List m_conditions; + private final OpenAcdRecipeActionRestInfo m_action; + private final String m_frequency; + + public OpenAcdRecipeStepRestInfo(OpenAcdRecipeStep step, OpenAcdRecipeActionRestInfo recipeActionRestInfo, List conditions) { + m_id = step.getId(); + m_conditions = conditions; + m_action = recipeActionRestInfo; + m_frequency = step.getFrequency(); + } + + public int getId() { + return m_id; + } + + public List getConditions() { + return m_conditions; + } + + public OpenAcdRecipeActionRestInfo getAction() { + return m_action; + } + + public String getFrequency() { + return m_frequency; + } + } + + static class OpenAcdRecipeConditionRestInfo { + private final String m_condition; + private final String m_relation; + private final String m_valueCondition; + + public OpenAcdRecipeConditionRestInfo(OpenAcdRecipeCondition condition) { + m_condition = condition.getCondition(); + m_relation = condition.getRelation(); + m_valueCondition = condition.getValueCondition(); + } + + public String getCondition() { + return m_condition; + } + + public String getRelation() { + return m_relation; + } + + public String getValueCondition() { + return m_valueCondition; + } + } + + static class OpenAcdClientRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + private final String m_identity; + + public OpenAcdClientRestInfo(OpenAcdClient client) { + m_id = client.getId(); + m_name = client.getName(); + m_description = client.getDescription(); + m_identity = client.getIdentity(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + + public String getIdentity() { + return m_identity; + } + } + + static class OpenAcdAgentGroupRestInfo { + private final int m_id; + private final String m_name; + private final String m_description; + + public OpenAcdAgentGroupRestInfo(OpenAcdAgentGroup agentGroup) { + m_id = agentGroup.getId(); + m_name = agentGroup.getName(); + m_description = agentGroup.getDescription(); + } + + public int getId() { + return m_id; + } + + public String getName() { + return m_name; + } + + public String getDescription() { + return m_description; + } + } + + static class OpenAcdAgentGroupRestInfoFull extends OpenAcdAgentGroupRestInfo { + private final List m_skills; + private final List m_queues; + private final List m_clients; + + public OpenAcdAgentGroupRestInfoFull(OpenAcdAgentGroup agentGroup, List skills, List queues, List clients) { + super(agentGroup); + m_skills = skills; + m_queues = queues; + m_clients = clients; + } + + public List getSkills() { + return m_skills; + } + + public List getQueues() { + return m_queues; + } + + public List getClients() { + return m_clients; + } + } + + static class OpenAcdReleaseCodeRestInfo { + private final int m_id; + private final String m_label; + private final String m_description; + private final int m_bias; + + public OpenAcdReleaseCodeRestInfo(OpenAcdReleaseCode releaseCode) { + m_id = releaseCode.getId(); + m_label = releaseCode.getLabel(); + m_bias = releaseCode.getBias(); + m_description = releaseCode.getDescription(); + } + + public int getId() { + return m_id; + } + + public String getLabel() { + return m_label; + } + + public String getDescription() { + return m_description; + } + + public int getBias() { + return m_bias; + } + } + + static class OpenAcdAgentRestInfoFull { + + private final int m_id; + private final int m_userId; + private final String m_userName; + private final String m_firstName; + private final String m_lastName; + private final int m_groupId; + private final String m_groupName; + private final String m_security; + private final List m_skills; + private final List m_queues; + private final List m_clients; + + public OpenAcdAgentRestInfoFull(OpenAcdAgent agent, List skills, List queues, List clients) { + m_id = agent.getId(); + m_firstName = agent.getFirstName(); + m_lastName = agent.getLastName(); + m_userId = agent.getUser().getId(); + m_userName = agent.getUser().getName(); + m_groupId = agent.getGroup().getId(); + m_groupName = agent.getGroup().getName(); + m_security = agent.getSecurity(); + m_skills = skills; + m_queues = queues; + m_clients = clients; + } + + public int getId() { + return m_id; + } + + public String getFirstName() { + return m_firstName; + } + + public String getLastName() { + return m_lastName; + } + + public int getUserId() { + return m_userId; + } + + public String getUserName() { + return m_userName; + } + + public int getGroupId() { + return m_groupId; + } + + public String getGroupName() { + return m_groupName; + } + + public String getSecurity() { + return m_security; + } + + public List getSkills() { + return m_skills; + } + + public List getQueues() { + return m_queues; + } + + public List getClients() { + return m_clients; + } + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupPermissionsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupPermissionsResource.java new file mode 100644 index 0000000000..25072f5b90 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupPermissionsResource.java @@ -0,0 +1,436 @@ +/* + * + * UserGroupPermissionsResource.java - A Restlet to read User Group data with Permissions from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.permission.Permission; +import org.sipfoundry.sipxconfig.permission.PermissionManager; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SettingBooleanRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.UserGroupPermissionRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.sipfoundry.sipxconfig.setting.Group; +import org.sipfoundry.sipxconfig.setting.SettingDao; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class UserGroupPermissionsResource extends UserResource { + + private SettingDao m_settingContext; // saveGroup is not available through corecontext + private PermissionManager m_permissionManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + // GET - Retrieve all and single User Group with Permissions + // --------------------------------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + UserGroupPermissionRestInfoFull userGroupPermissionRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + userGroupPermissionRestInfo = createUserGroupPermissionRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read User Group failed", exception.getLocalizedMessage()); + } + + return new UserGroupPermissionRepresentation(variant.getMediaType(), userGroupPermissionRestInfo); + } + + + // if not single, process request for all + List userGroups = getCoreContext().getGroups(); // settingsContext.getGroups() requires Resource string value + + List userGroupPermissionsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortUserGroups(userGroups); + + // set requested items and get resulting metadata + metadataRestInfo = addUserGroups(userGroupPermissionsRestInfo, userGroups); + + // create final restinfo + UserGroupPermissionsBundleRestInfo userGroupPermissionsBundleRestInfo = new UserGroupPermissionsBundleRestInfo(userGroupPermissionsRestInfo, metadataRestInfo); + + return new UserGroupPermissionsRepresentation(variant.getMediaType(), userGroupPermissionsBundleRestInfo); + } + + // PUT - Update Permissions + // ------------------------ + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + UserGroupPermissionRepresentation representation = new UserGroupPermissionRepresentation(entity); + UserGroupPermissionRestInfoFull userGroupPermissionRestInfo = representation.getObject(); + Group userGroup = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(userGroupPermissionRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + userGroup = m_settingContext.getGroup(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateUserGroupPermission(userGroup, userGroupPermissionRestInfo); + m_settingContext.saveGroup(userGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update User Group Permissions failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated User Group Permissions", userGroup.getId()); + + return; + } + + + // otherwise error, since no creation of new permissions + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Missing ID"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(UserGroupPermissionRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private UserGroupPermissionRestInfoFull createUserGroupPermissionRestInfo(int id) { + Group group = m_settingContext.getGroup(id); + + return createUserGroupPermissionRestInfo(group); + } + + private UserGroupPermissionRestInfoFull createUserGroupPermissionRestInfo(Group group) { + UserGroupPermissionRestInfoFull userGroupPermissionRestInfo = null; + List settings; + + settings = createSettingsRestInfo(group); + userGroupPermissionRestInfo = new UserGroupPermissionRestInfoFull(group, settings); + + return userGroupPermissionRestInfo; + } + + private List createSettingsRestInfo(Group group) { + List settings = new ArrayList(); + SettingBooleanRestInfo settingRestInfo = null; + Collection permissions; + String permissionName; + String permissionValue; + boolean defaultValue; + + permissions = m_permissionManager.getPermissions(); + + // settings value for permissions are ENABLE or DISABLE instead of boolean + for (Permission permission : permissions) { + permissionName = permission.getName(); + + try { + // empty return means setting is at default (unless error in input to getSettingValue) + //permissionValue = group.getSettingValue(PermissionName.findByName(permissionName).getPath()); + permissionValue = group.getSettingValue(permission.getSettingPath()); + } + catch (Exception exception) { + permissionValue = "GetSettingValue error: " + exception.getLocalizedMessage(); + } + + defaultValue = permission.getDefaultValue(); + + settingRestInfo = new SettingBooleanRestInfo(permissionName, permissionValue, defaultValue); + settings.add(settingRestInfo); + } + + return settings; + } + + private MetadataRestInfo addUserGroups(List userGroupPermissionsRestInfo, List userGroups) { + UserGroupPermissionRestInfoFull userGroupPermissionRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, userGroups.size()); + + // create list of restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + Group userGroup = userGroups.get(index); + + userGroupPermissionRestInfo = createUserGroupPermissionRestInfo(userGroup); + userGroupPermissionsRestInfo.add(userGroupPermissionRestInfo); + } + + // create metadata about restinfos + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortUserGroups(List userGroups) { + // sort if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group1.getName().compareToIgnoreCase(group2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group1.getDescription().compareToIgnoreCase(group2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group2.getName().compareToIgnoreCase(group1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group2.getDescription().compareToIgnoreCase(group1.getDescription()); + } + + }); + break; + } + } + } + + private void updateUserGroupPermission(Group userGroup, UserGroupPermissionRestInfoFull userGroupPermissionRestInfo) { + Permission permission; + + // update each permission setting + for (SettingBooleanRestInfo settingRestInfo : userGroupPermissionRestInfo.getPermissions()) { + permission = m_permissionManager.getPermissionByName(settingRestInfo.getName()); + userGroup.setSettingValue(permission.getSettingPath(), settingRestInfo.getValue()); + } + } + + + // REST Representations + // -------------------- + + static class UserGroupPermissionsRepresentation extends XStreamRepresentation { + + public UserGroupPermissionsRepresentation(MediaType mediaType, UserGroupPermissionsBundleRestInfo object) { + super(mediaType, object); + } + + public UserGroupPermissionsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("user-group-permission", UserGroupPermissionsBundleRestInfo.class); + xstream.alias("group", UserGroupPermissionRestInfoFull.class); + xstream.alias("setting", SettingBooleanRestInfo.class); + } + } + + static class UserGroupPermissionRepresentation extends XStreamRepresentation { + + public UserGroupPermissionRepresentation(MediaType mediaType, UserGroupPermissionRestInfoFull object) { + super(mediaType, object); + } + + public UserGroupPermissionRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", UserGroupPermissionRestInfoFull.class); + xstream.alias("setting", SettingBooleanRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class UserGroupPermissionsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_groups; + + public UserGroupPermissionsBundleRestInfo(List userGroupPermissions, MetadataRestInfo metadata) { + m_metadata = metadata; + m_groups = userGroupPermissions; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getGroups() { + return m_groups; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setSettingDao(SettingDao settingContext) { + m_settingContext = settingContext; + } + + @Required + public void setPermissionManager(PermissionManager permissionManager) { + m_permissionManager = permissionManager; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupsResource.java new file mode 100644 index 0000000000..f72ad31d0c --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserGroupsResource.java @@ -0,0 +1,500 @@ +/* + * + * UserGroupsResource.java - A Restlet to read User Group data from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.branch.Branch; +import org.sipfoundry.sipxconfig.branch.BranchManager; +import org.sipfoundry.sipxconfig.common.CoreContext; +import org.sipfoundry.sipxconfig.rest.RestUtilities.BranchRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.UserGroupRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.sipfoundry.sipxconfig.setting.Group; +import org.sipfoundry.sipxconfig.setting.SettingDao; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class UserGroupsResource extends UserResource { + + private SettingDao m_settingContext; // saveGroup is not available through corecontext + private BranchManager m_branchManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + NAME, DESCRIPTION, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single Skill + // ----------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + UserGroupRestInfoFull userGroupRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + userGroupRestInfo = createUserGroupRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read User Group failed", exception.getLocalizedMessage()); + } + + return new UserGroupRepresentation(variant.getMediaType(), userGroupRestInfo); + } + + + // if not single, process request for all + List userGroups = getCoreContext().getGroups(); // settingsContext.getGroups() requires Resource string value + + List userGroupsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortUserGroups(userGroups); + + // set requested items and get resulting metadata + metadataRestInfo = addUserGroups(userGroupsRestInfo, userGroups); + + // create final restinfo + UserGroupsBundleRestInfo userGroupsBundleRestInfo = new UserGroupsBundleRestInfo(userGroupsRestInfo, metadataRestInfo); + + return new UserGroupsRepresentation(variant.getMediaType(), userGroupsBundleRestInfo); + } + + // PUT - Update or Add single Skill + // -------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + UserGroupRepresentation representation = new UserGroupRepresentation(entity); + UserGroupRestInfoFull userGroupRestInfo = representation.getObject(); + Group userGroup = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(userGroupRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + userGroup = m_settingContext.getGroup(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateUserGroup(userGroup, userGroupRestInfo); + m_settingContext.saveGroup(userGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update User Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated User Group", userGroup.getId()); + + return; + } + + + // otherwise add new + try { + userGroup = createUserGroup(userGroupRestInfo); + m_settingContext.saveGroup(userGroup); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create User Group failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created User Group", userGroup.getId()); + } + + + // DELETE - Delete single Skill + // ---------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + Group userGroup; + int idInt; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + userGroup = m_settingContext.getGroup(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + List userGroupIds = new ArrayList(); + userGroupIds.add(idInt); + m_settingContext.deleteGroups(userGroupIds); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted User Group", userGroup.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(UserGroupRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private UserGroupRestInfoFull createUserGroupRestInfo(int id) { + Group group = m_settingContext.getGroup(id); + + return createUserGroupRestInfo(group); + } + + private UserGroupRestInfoFull createUserGroupRestInfo(Group group) { + UserGroupRestInfoFull userGroupRestInfo = null; + BranchRestInfoFull branchRestInfo = null; + Branch branch = null; + + // group may not have branch assigned + branch = group.getBranch(); + if (branch != null) { + branchRestInfo = createBranchRestInfo(branch.getId()); + } + + userGroupRestInfo = new UserGroupRestInfoFull(group, branchRestInfo); + + return userGroupRestInfo; + } + + private BranchRestInfoFull createBranchRestInfo(int id) { + BranchRestInfoFull branchRestInfo = null; + + Branch branch = m_branchManager.getBranch(id); + branchRestInfo = new BranchRestInfoFull(branch); + + return branchRestInfo; + } + + private MetadataRestInfo addUserGroups(List userGroupsRestInfo, List userGroups) { + UserGroupRestInfoFull userGroupRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, userGroups.size()); + + // create list of restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + Group userGroup = userGroups.get(index); + + userGroupRestInfo = createUserGroupRestInfo(userGroup); + userGroupsRestInfo.add(userGroupRestInfo); + } + + // create metadata about restinfos + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortUserGroups(List userGroups) { + // sort if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case NAME: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group1.getName().compareToIgnoreCase(group2.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group1.getDescription().compareToIgnoreCase(group2.getDescription()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case NAME: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group2.getName().compareToIgnoreCase(group1.getName()); + } + + }); + break; + + case DESCRIPTION: + Collections.sort(userGroups, new Comparator() { + + public int compare(Object object1, Object object2) { + Group group1 = (Group) object1; + Group group2 = (Group) object2; + return group2.getDescription().compareToIgnoreCase(group1.getDescription()); + } + + }); + break; + } + } + } + + private void updateUserGroup(Group userGroup, UserGroupRestInfoFull userGroupRestInfo) { + Branch branch; + String tempString; + + // do not allow empty name + tempString = userGroupRestInfo.getName(); + if (!tempString.isEmpty()) { + userGroup.setName(tempString); + } + + userGroup.setDescription(userGroupRestInfo.getDescription()); + + branch = getBranch(userGroupRestInfo); + userGroup.setBranch(branch); + } + + private Group createUserGroup(UserGroupRestInfoFull userGroupRestInfo) { + Branch branch = null; + Group userGroup = new Group(); + + // copy fields from rest info + userGroup.setName(userGroupRestInfo.getName()); + userGroup.setDescription(userGroupRestInfo.getDescription()); + + // apparently there is a special Resource value for user groups + userGroup.setResource(CoreContext.USER_GROUP_RESOURCE_ID); + + branch = getBranch(userGroupRestInfo); + userGroup.setBranch(branch); + + return userGroup; + } + + private Branch getBranch(UserGroupRestInfoFull userGroupRestInfo) { + Branch branch = null; + BranchRestInfoFull branchRestInfo = userGroupRestInfo.getBranch(); + + if (branchRestInfo != null) { + branch = m_branchManager.getBranch(branchRestInfo.getId()); + } + + return branch; + } + + + // REST Representations + // -------------------- + + static class UserGroupsRepresentation extends XStreamRepresentation { + + public UserGroupsRepresentation(MediaType mediaType, UserGroupsBundleRestInfo object) { + super(mediaType, object); + } + + public UserGroupsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("user-group", UserGroupsBundleRestInfo.class); + xstream.alias("group", UserGroupRestInfoFull.class); + } + } + + static class UserGroupRepresentation extends XStreamRepresentation { + + public UserGroupRepresentation(MediaType mediaType, UserGroupRestInfoFull object) { + super(mediaType, object); + } + + public UserGroupRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", UserGroupRestInfoFull.class); + } + } + + + // REST info objects + // ----------------- + + static class UserGroupsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_groups; + + public UserGroupsBundleRestInfo(List userGroups, MetadataRestInfo metadata) { + m_metadata = metadata; + m_groups = userGroups; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getGroups() { + return m_groups; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setSettingDao(SettingDao settingContext) { + m_settingContext = settingContext; + } + + @Required + public void setBranchManager(BranchManager branchManager) { + m_branchManager = branchManager; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserPermissionsResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserPermissionsResource.java new file mode 100644 index 0000000000..4e2ffcf2fa --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UserPermissionsResource.java @@ -0,0 +1,516 @@ +/* + * + * UserGroupPermissionsResource.java - A Restlet to read User Group data with Permissions from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.common.User; +import org.sipfoundry.sipxconfig.permission.Permission; +import org.sipfoundry.sipxconfig.permission.PermissionManager; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SettingBooleanRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class UserPermissionsResource extends UserResource { + + private PermissionManager m_permissionManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + LASTNAME, FIRSTNAME, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + // GET - Retrieve all and single User with Permissions + // --------------------------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + UserPermissionRestInfoFull userPermissionRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + userPermissionRestInfo = createUserPermissionRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read User failed", exception.getLocalizedMessage()); + } + + return new UserPermissionRepresentation(variant.getMediaType(), userPermissionRestInfo); + } + + + // if not single, check if need to filter list + List users; + Collection userIds; + + String branchIdString = m_form.getFirstValue("branch"); + String idListString = m_form.getFirstValue("ids"); + int branchId; + + if ((branchIdString != null) && (branchIdString != "")) { + try { + branchId = RestUtilities.getIntFromAttribute(branchIdString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Branch ID " + branchIdString + " not found."); + } + + userIds = getCoreContext().getBranchMembersByPage(branchId, 0, getCoreContext().getBranchMembersCount(branchId)); + users = getUsers(userIds); + } + else if ((idListString != null) && (!idListString.isEmpty())) { + // searching by id list + String[] idArray = idListString.split(","); + + users = new ArrayList(); + User user; + for (String id : idArray) { + try { + idInt = RestUtilities.getIntFromAttribute(id); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + id + " not found."); + } + + user = getCoreContext().getUser(idInt); + users.add(user); + } + } + else { + // process request for all + users = getCoreContext().loadUsersByPage(1, getCoreContext().getAllUsersCount()); // no GetUsers() in coreContext, instead some subgroups + } + + List userPermissionsRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortUsers(users); + + // set requested items and get resulting metadata + metadataRestInfo = addUsers(userPermissionsRestInfo, users); + + // create final restinfo + UserPermissionsBundleRestInfo userPermissionsBundleRestInfo = new UserPermissionsBundleRestInfo(userPermissionsRestInfo, metadataRestInfo); + + return new UserPermissionsRepresentation(variant.getMediaType(), userPermissionsBundleRestInfo); + } + + // PUT - Update Permissions + // ------------------------ + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + UserPermissionRepresentation representation = new UserPermissionRepresentation(entity); + UserPermissionRestInfoFull userPermissionRestInfo = representation.getObject(); + User user = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(userPermissionRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + user = getCoreContext().getUser(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateUserPermission(user, userPermissionRestInfo); + getCoreContext().saveUser(user); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update User Permissions failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated User Permissions", user.getId()); + + return; + } + + + // otherwise error, since no creation of new permissions + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Missing ID"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(UserPermissionRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private UserPermissionRestInfoFull createUserPermissionRestInfo(int id) { + User user = getCoreContext().getUser(id); + + return createUserPermissionRestInfo(user); + } + + private UserPermissionRestInfoFull createUserPermissionRestInfo(User user) { + UserPermissionRestInfoFull userPermissionRestInfo = null; + List settings; + + settings = createSettingsRestInfo(user); + userPermissionRestInfo = new UserPermissionRestInfoFull(user, settings); + + return userPermissionRestInfo; + } + + private List createSettingsRestInfo(User user) { + List settings = new ArrayList(); + SettingBooleanRestInfo settingRestInfo = null; + Collection permissions; + String permissionName; + String permissionValue; + boolean defaultValue; + + permissions = m_permissionManager.getPermissions(); + + // settings value for permissions are ENABLE or DISABLE instead of boolean + for (Permission permission : permissions) { + permissionName = permission.getName(); + + try { + // empty return means setting is at default (unless error in input to getSettingValue) + //permissionValue = group.getSettingValue(PermissionName.findByName(permissionName).getPath()); + permissionValue = user.getSettingValue(permission.getSettingPath()); + } + catch (Exception exception) { + permissionValue = "GetSettingValue error: " + exception.getLocalizedMessage(); + } + + defaultValue = permission.getDefaultValue(); + + settingRestInfo = new SettingBooleanRestInfo(permissionName, permissionValue, defaultValue); + settings.add(settingRestInfo); + } + + return settings; + } + + private MetadataRestInfo addUsers(List userPermissionsRestInfo, List users) { + UserPermissionRestInfoFull userPermissionRestInfo; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, users.size()); + + // create list of restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + User user = users.get(index); + + userPermissionRestInfo = createUserPermissionRestInfo(user); + userPermissionsRestInfo.add(userPermissionRestInfo); + } + + // create metadata about restinfos + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortUsers(List users) { + // sort if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case LASTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user1.getLastName().compareToIgnoreCase(user2.getLastName()); + } + + }); + break; + + case FIRSTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user1.getFirstName().compareToIgnoreCase(user2.getFirstName()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case LASTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user2.getLastName().compareToIgnoreCase(user1.getLastName()); + } + + }); + break; + + case FIRSTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user2.getFirstName().compareToIgnoreCase(user1.getFirstName()); + } + + }); + break; + } + } + } + + private List getUsers(Collection userIds) { + List users; + + users = new ArrayList(); + for (int userId : userIds) { + users.add(getCoreContext().getUser(userId)); + } + + return users; + } + + private void updateUserPermission(User user, UserPermissionRestInfoFull userPermissionRestInfo) { + Permission permission; + + // update each permission setting + for (SettingBooleanRestInfo settingRestInfo : userPermissionRestInfo.getPermissions()) { + permission = m_permissionManager.getPermissionByName(settingRestInfo.getName()); + user.setSettingValue(permission.getSettingPath(), settingRestInfo.getValue()); + } + } + + + // REST Representations + // -------------------- + + static class UserPermissionsRepresentation extends XStreamRepresentation { + + public UserPermissionsRepresentation(MediaType mediaType, UserPermissionsBundleRestInfo object) { + super(mediaType, object); + } + + public UserPermissionsRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("user-permission", UserPermissionsBundleRestInfo.class); + xstream.alias("user", UserPermissionRestInfoFull.class); + xstream.alias("setting", SettingBooleanRestInfo.class); + } + } + + static class UserPermissionRepresentation extends XStreamRepresentation { + + public UserPermissionRepresentation(MediaType mediaType, UserPermissionRestInfoFull object) { + super(mediaType, object); + } + + public UserPermissionRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("user", UserPermissionRestInfoFull.class); + xstream.alias("setting", SettingBooleanRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class UserPermissionsBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_users; + + public UserPermissionsBundleRestInfo(List userPermissions, MetadataRestInfo metadata) { + m_metadata = metadata; + m_users = userPermissions; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getUsers() { + return m_users; + } + } + + static class UserRestInfo { + private final int m_id; + private final String m_lastName; + private final String m_firstName; + + public UserRestInfo(User user) { + m_id = user.getId(); + m_lastName = user.getLastName(); + m_firstName = user.getFirstName(); + } + + public int getId() { + return m_id; + } + + public String getLastName() { + return m_lastName; + } + + public String getFirstName() { + return m_firstName; + } + } + + static class UserPermissionRestInfoFull extends UserRestInfo { + private final List m_permissions; + + public UserPermissionRestInfoFull(User user, List settingsRestInfo) { + super(user); + + m_permissions = settingsRestInfo; + } + + public List getPermissions() { + return m_permissions; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setPermissionManager(PermissionManager permissionManager) { + m_permissionManager = permissionManager; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UsersResource.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UsersResource.java new file mode 100644 index 0000000000..abb46e4ff9 --- /dev/null +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/UsersResource.java @@ -0,0 +1,665 @@ +/* + * + * UsersResource.java - A Restlet to read User data from SipXecs + * Copyright (C) 2012 PATLive, D. Chang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package org.sipfoundry.sipxconfig.rest; + +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.restlet.Context; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Request; +import org.restlet.data.Response; +import org.restlet.data.Status; +import org.restlet.resource.Representation; +import org.restlet.resource.ResourceException; +import org.restlet.resource.Variant; +import org.sipfoundry.sipxconfig.branch.Branch; +import org.sipfoundry.sipxconfig.branch.BranchManager; +import org.sipfoundry.sipxconfig.common.User; +import org.sipfoundry.sipxconfig.rest.RestUtilities.AliasRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.BranchRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.BranchRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.MetadataRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.PaginationInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.SortInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.UserGroupRestInfo; +import org.sipfoundry.sipxconfig.rest.RestUtilities.UserRestInfoFull; +import org.sipfoundry.sipxconfig.rest.RestUtilities.ValidationInfo; +import org.sipfoundry.sipxconfig.setting.Group; +import org.springframework.beans.factory.annotation.Required; + +import com.thoughtworks.xstream.XStream; + +public class UsersResource extends UserResource { + + private BranchManager m_branchManager; + private Form m_form; + + // use to define all possible sort fields + private enum SortField { + USERNAME, LASTNAME, FIRSTNAME, NONE; + + public static SortField toSortField(String fieldString) { + if (fieldString == null) { + return NONE; + } + + try { + return valueOf(fieldString.toUpperCase()); + } + catch (Exception ex) { + return NONE; + } + } + } + + + @Override + public void init(Context context, Request request, Response response) { + super.init(context, request, response); + getVariants().add(new Variant(TEXT_XML)); + getVariants().add(new Variant(APPLICATION_JSON)); + + // pull parameters from url + m_form = getRequest().getResourceRef().getQueryAsForm(); + } + + + // Allowed REST operations + // ----------------------- + + @Override + public boolean allowGet() { + return true; + } + + @Override + public boolean allowPut() { + return true; + } + + @Override + public boolean allowDelete() { + return true; + } + + // GET - Retrieve all and single User + // ---------------------------------- + + @Override + public Representation represent(Variant variant) throws ResourceException { + // process request for single + int idInt; + UserRestInfoFull userRestInfo = null; + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + idInt = RestUtilities.getIntFromAttribute(idString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + } + + try { + userRestInfo = createUserRestInfo(idInt); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_READ_FAILED, "Read User failed", exception.getLocalizedMessage()); + } + + return new UserRepresentation(variant.getMediaType(), userRestInfo); + } + + + // if not single, check if need to filter list + List users; + Collection userIds; + + String branchIdString = m_form.getFirstValue("branch"); + String idListString = m_form.getFirstValue("ids"); + int branchId; + + // check if searching by branch + if ((branchIdString != null) && (!branchIdString.isEmpty())) { + try { + branchId = RestUtilities.getIntFromAttribute(branchIdString); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "Branch ID " + branchIdString + " not found."); + } + + userIds = getCoreContext().getBranchMembersByPage(branchId, 0, getCoreContext().getBranchMembersCount(branchId)); + users = getUsers(userIds); + } + else if ((idListString != null) && (!idListString.isEmpty())) { + // searching by id list + String[] idArray = idListString.split(","); + + users = new ArrayList(); + User user; + for (String id : idArray) { + try { + idInt = RestUtilities.getIntFromAttribute(id); + } + catch (Exception exception) { + return RestUtilities.getResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + id + " not found."); + } + + user = getCoreContext().getUser(idInt); + users.add(user); + } + } + else { + // process request for all + users = getCoreContext().loadUsersByPage(1, getCoreContext().getAllUsersCount()); // no GetUsers() in coreContext, instead some subgroups + } + + List usersRestInfo = new ArrayList(); + MetadataRestInfo metadataRestInfo; + + // sort if specified + sortUsers(users); + + // set requested items and get resulting metadata + metadataRestInfo = addUsers(usersRestInfo, users); + + // create final restinfo + UsersBundleRestInfo usersBundleRestInfo = new UsersBundleRestInfo(usersRestInfo, metadataRestInfo); + + return new UsersRepresentation(variant.getMediaType(), usersBundleRestInfo); + } + + // PUT - Update or Add single User + // ------------------------------- + + @Override + public void storeRepresentation(Representation entity) throws ResourceException { + // get from request body + UserRepresentation representation = new UserRepresentation(entity); + UserRestInfoFull userRestInfo = representation.getObject(); + User user = null; + + // validate input for update or create + ValidationInfo validationInfo = validate(userRestInfo); + + if (!validationInfo.valid) { + RestUtilities.setResponseError(getResponse(), validationInfo.responseCode, validationInfo.message); + return; + } + + + // if have id then update single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + user = getCoreContext().getUser(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + // copy values over to existing + try { + updateUser(user, userRestInfo); + getCoreContext().saveUser(user); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Update User failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_UPDATED, "Updated User", user.getId()); + + return; + } + + + // otherwise add new + try { + user = createUser(userRestInfo); + getCoreContext().saveUser(user); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_WRITE_FAILED, "Create User failed", exception.getLocalizedMessage()); + return; + } + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_CREATED, "Created User", user.getId()); + } + + + // DELETE - Delete single User + // --------------------------- + + @Override + public void removeRepresentations() throws ResourceException { + User user; + + // get id then delete single + String idString = (String) getRequest().getAttributes().get("id"); + + if (idString != null) { + try { + int idInt = RestUtilities.getIntFromAttribute(idString); + user = getCoreContext().getUser(idInt); + } + catch (Exception exception) { + RestUtilities.setResponseError(getResponse(), RestUtilities.ResponseCode.ERROR_BAD_INPUT, "ID " + idString + " not found."); + return; + } + + getCoreContext().deleteUser(user); + + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.SUCCESS_DELETED, "Deleted User", user.getId()); + + return; + } + + // no id string + RestUtilities.setResponse(getResponse(), RestUtilities.ResponseCode.ERROR_MISSING_INPUT, "ID value missing"); + } + + + // Helper functions + // ---------------- + + // basic interface level validation of data provided through REST interface for creation or + // update + // may also contain clean up of input data + // may create another validation function if different rules needed for update v. create + private ValidationInfo validate(UserRestInfoFull restInfo) { + ValidationInfo validationInfo = new ValidationInfo(); + + return validationInfo; + } + + private UserRestInfoFull createUserRestInfo(int id) throws ResourceException { + User user = getCoreContext().getUser(id); + + if (user == null) { + throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "No user with id " + id); + } + + return createUserRestInfo(user); + } + + private UserRestInfoFull createUserRestInfo(User user) { + UserRestInfoFull userRestInfo = null; + UserGroupRestInfo userGroupRestInfo = null; + List userGroupsRestInfo = new ArrayList(); + Set groups = null; + BranchRestInfo branchRestInfo = null; + Branch branch = null; + AliasRestInfo aliasRestInfo = null; + List aliasesRestInfo = new ArrayList(); + Set aliases = null; + + groups = user.getGroups(); + + // user does not necessarily have any groups + if ((groups != null) && (!groups.isEmpty())) { + for (Group group : groups) { + userGroupRestInfo = new UserGroupRestInfo(group); + userGroupsRestInfo.add(userGroupRestInfo); + } + } + + branch = user.getBranch(); + + // user does not necessarily have branch + if (branch != null) { + branchRestInfo = new BranchRestInfo(branch); + } + + aliases = user.getAliases(); + + // user does not necessarily have any aliases + if (aliases != null) { + for (String alias : aliases) { + aliasRestInfo = new AliasRestInfo(alias); + aliasesRestInfo.add(aliasRestInfo); + } + } + + userRestInfo = new UserRestInfoFull(user, userGroupsRestInfo, branchRestInfo, aliasesRestInfo); + + return userRestInfo; + } + + private MetadataRestInfo addUsers(List usersRestInfo, List users) { + UserRestInfoFull userRestInfo; + User user; + + // determine pagination + PaginationInfo paginationInfo = RestUtilities.calculatePagination(m_form, users.size()); + + + // create list of skill restinfos + for (int index = paginationInfo.startIndex; index <= paginationInfo.endIndex; index++) { + user = users.get(index); + + userRestInfo = createUserRestInfo(user); + usersRestInfo.add(userRestInfo); + } + + + // create metadata about agent groups + MetadataRestInfo metadata = new MetadataRestInfo(paginationInfo); + return metadata; + } + + private void sortUsers(List users) { + // sort groups if requested + SortInfo sortInfo = RestUtilities.calculateSorting(m_form); + + if (!sortInfo.sort) { + return; + } + + SortField sortField = SortField.toSortField(sortInfo.sortField); + + if (sortInfo.directionForward) { + + switch (sortField) { + case USERNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user1.getUserName().compareToIgnoreCase(user2.getUserName()); + } + + }); + break; + + case LASTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user1.getLastName().compareToIgnoreCase(user2.getLastName()); + } + + }); + break; + + + case FIRSTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user1.getFirstName().compareToIgnoreCase(user2.getFirstName()); + } + + }); + break; + } + } + else { + // must be reverse + switch (sortField) { + case USERNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user2.getUserName().compareToIgnoreCase(user1.getUserName()); + } + + }); + break; + + case LASTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user2.getLastName().compareToIgnoreCase(user1.getLastName()); + } + + }); + break; + + case FIRSTNAME: + Collections.sort(users, new Comparator() { + + public int compare(Object object1, Object object2) { + User user1 = (User) object1; + User user2 = (User) object2; + return user2.getFirstName().compareToIgnoreCase(user1.getFirstName()); + } + + }); + break; + } + } + } + + private List getUsers(Collection userIds) { + List users; + + users = new ArrayList(); + for (int userId : userIds) { + users.add(getCoreContext().getUser(userId)); + } + + return users; + } + + private void updateUser(User user, UserRestInfoFull userRestInfo) { + Branch branch; + String tempString; + + // do not allow empty username + tempString = userRestInfo.getUserName(); + if (!tempString.isEmpty()) { + user.setUserName(tempString); + } + + user.setLastName(userRestInfo.getLastName()); + user.setFirstName(userRestInfo.getFirstName()); + user.setSipPassword(userRestInfo.getSipPassword()); + user.setEmailAddress(userRestInfo.getEmailAddress()); + + // if pin is empty do not save + if (!userRestInfo.getPin().isEmpty()) { + user.setPin(userRestInfo.getPin(), getCoreContext().getAuthorizationRealm()); + } + + // user may not have any groups + List userGroupsRestInfo = userRestInfo.getGroups(); + if (userGroupsRestInfo != null) { + user.setGroups(createUserGroups(userRestInfo)); + } + else { + user.setGroups(null); + } + + // user may not have a branch + if (userRestInfo.getBranch() != null) { + branch = m_branchManager.getBranch(userRestInfo.getBranch().getId()); + user.setBranch(branch); + } + else { + user.setBranch(null); + } + + // user may not have any aliases + if (userRestInfo.getAliases() != null) { + user.setAliases(createAliases(userRestInfo)); + } + else { + user.setAliases(null); + } + } + + private User createUser(UserRestInfoFull userRestInfo) { + User user = getCoreContext().newUser(); + Branch branch; + + user.setUserName(userRestInfo.getUserName()); + user.setLastName(userRestInfo.getLastName()); + user.setFirstName(userRestInfo.getFirstName()); + user.setSipPassword(userRestInfo.getSipPassword()); + user.setEmailAddress(userRestInfo.getEmailAddress()); + + // if pin is empty do not save + if (!userRestInfo.getPin().isEmpty()) { + user.setPin(userRestInfo.getPin(), getCoreContext().getAuthorizationRealm()); + } + + // user may not have any groups + List userGroupsRestInfo = userRestInfo.getGroups(); + if (userGroupsRestInfo != null) { + user.setGroups(createUserGroups(userRestInfo)); + } + + // user may not have a branch + if (userRestInfo.getBranch() != null) { + branch = m_branchManager.getBranch(userRestInfo.getBranch().getId()); + user.setBranch(branch); + } + + // user may not have any aliases + if (userRestInfo.getAliases() != null) { + user.setAliases(createAliases(userRestInfo)); + } + + return user; + } + + private Set createUserGroups(UserRestInfoFull userRestInfo) { + Set userGroups = new TreeSet(); + Group userGroup; + + for (UserGroupRestInfo userGroupRestInfo : userRestInfo.getGroups()) { + userGroup = getCoreContext().getGroupById(userGroupRestInfo.getId()); + userGroups.add(userGroup); + } + + return userGroups; + } + + private Set createAliases(UserRestInfoFull userRestInfo) { + Set aliases = new LinkedHashSet(); + + for (AliasRestInfo aliasRestInfo : userRestInfo.getAliases()) { + aliases.add(aliasRestInfo.getAlias()); + } + + return aliases; + } + + + // REST Representations + // -------------------- + + static class UsersRepresentation extends XStreamRepresentation { + + public UsersRepresentation(MediaType mediaType, UsersBundleRestInfo object) { + super(mediaType, object); + } + + public UsersRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("user", UsersBundleRestInfo.class); + xstream.alias("user", UserRestInfoFull.class); + xstream.alias("group", UserGroupRestInfo.class); + xstream.alias("branch", BranchRestInfoFull.class); + xstream.alias("alias", AliasRestInfo.class); + } + } + + static class UserRepresentation extends XStreamRepresentation { + + public UserRepresentation(MediaType mediaType, UserRestInfoFull object) { + super(mediaType, object); + } + + public UserRepresentation(Representation representation) { + super(representation); + } + + @Override + protected void configureXStream(XStream xstream) { + xstream.alias("group", UserGroupRestInfo.class); + xstream.alias("user", UserRestInfoFull.class); + xstream.alias("branch", BranchRestInfoFull.class); + xstream.alias("alias", AliasRestInfo.class); + } + } + + + // REST info objects + // ----------------- + + static class UsersBundleRestInfo { + private final MetadataRestInfo m_metadata; + private final List m_users; + + public UsersBundleRestInfo(List users, MetadataRestInfo metadata) { + m_metadata = metadata; + m_users = users; + } + + public MetadataRestInfo getMetadata() { + return m_metadata; + } + + public List getSkills() { + return m_users; + } + } + + + // Injected objects + // ---------------- + + @Required + public void setBranchManager(BranchManager branchManager) { + m_branchManager = branchManager; + } + +} diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/rest.beans.xml b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/rest.beans.xml index 219b631e4e..db4682867b 100644 --- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/rest.beans.xml +++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/rest/rest.beans.xml @@ -224,4 +224,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +