Author: ptillemans
Date: 2006-10-18 16:51:29-0700
New Revision: 10311
Added:
trunk/src/java/org/tigris/scarab/xmlrpc/NewTicketHandler.java
trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateException.java (contents, props changed)
trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHandler.java (contents, props changed)
trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHelper.java (contents, props changed)
trunk/src/test/org/tigris/scarab/xmlrpc/NewTicketHandlerTest.java
trunk/xdocs/howto/ldap-howto.xml
Log:
NewTicketHandler xmlrpc service to create and prefill new tickets using a simple pragmatic interface.
ScarabUpdateHandler xmlrpc service to synchronize dropdown lists with external datasources.
Added a howto to configure the LDAP authentication.
Added: trunk/src/java/org/tigris/scarab/xmlrpc/NewTicketHandler.java
Url: http://scarab.tigris.org/source/browse/scarab/trunk/src/java/org/tigris/scarab/xmlrpc/NewTicketHandler.java?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/src/java/org/tigris/scarab/xmlrpc/NewTicketHandler.java 2006-10-18 16:51:29-0700
@@ -0,0 +1,166 @@
+package org.tigris.scarab.xmlrpc;
+/* ====================================================================
+*
+* Copyright (c) 2006 CollabNet.
+*
+* Licensed under the
+*
+* CollabNet/Tigris.org Apache-style license (the "License");
+*
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://scarab.tigris.org/LICENSE
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+* implied. See the License for the specific language governing
+* permissions and limitations under the License.
+* ====================================================================
+*
+* This software consists of voluntary contributions made by many
+* individuals on behalf of CollabNet.
+*
+* [Additional notices, if required by prior licensing conditions]
+*
+*/
+
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.MapIterator;
+import org.apache.commons.collections.map.LinkedMap;
+import org.apache.fulcrum.intake.model.Field;
+import org.apache.fulcrum.intake.model.Group;
+import org.apache.log4j.Category;
+import org.apache.torque.TorqueException;
+import org.apache.torque.util.Criteria;
+import org.tigris.scarab.attribute.DateAttribute;
+import org.tigris.scarab.attribute.OptionAttribute;
+import org.tigris.scarab.notification.ActivityType;
+import org.tigris.scarab.notification.NotificationManagerFactory;
+import org.tigris.scarab.om.ActivityManager;
+import org.tigris.scarab.om.ActivitySet;
+import org.tigris.scarab.om.Attachment;
+import org.tigris.scarab.om.AttachmentManager;
+import org.tigris.scarab.om.AttachmentType;
+import org.tigris.scarab.om.AttachmentTypeManager;
+import org.tigris.scarab.om.Attribute;
+import org.tigris.scarab.om.AttributeManager;
+import org.tigris.scarab.om.AttributeOption;
+import org.tigris.scarab.om.AttributeOptionManager;
+import org.tigris.scarab.om.AttributeValue;
+import org.tigris.scarab.om.AttributeValueManager;
+import org.tigris.scarab.om.Issue;
+import org.tigris.scarab.om.IssueType;
+import org.tigris.scarab.om.IssueTypeManager;
+import org.tigris.scarab.om.Module;
+import org.tigris.scarab.om.ScarabModulePeer;
+import org.tigris.scarab.om.ScarabUser;
+import org.tigris.scarab.om.ScarabUserManager;
+import org.tigris.scarab.tools.localization.L10NKeySet;
+import org.tigris.scarab.util.ScarabException;
+
+/**
+ * @author pti
+ *
+ */
+public class NewTicketHandler {
+ Category logger = Category.getInstance(NewTicketHandler.class);
+
+ /**
+ * @param module The module to add the ticket to
+ * @param issueType The issuetype as which the ticket should be entered
+ * @param user The user as which the ticket should be entered
+ * @param attribs A map with the attributes to be entered
+ * @return
+ * @throws TorqueException
+ * @throws ScarabException
+ */
+ public String createNewTicket( String moduleName,
+ String issueTypeName,
+ String userName,
+ Hashtable attribs) throws TorqueException, ScarabException {
+ Module module = getModuleByCode(moduleName);
+ IssueType issueType = IssueType.getInstance(issueTypeName);
+ Issue issue = module.getNewIssue(issueType);
+ ScarabUser user = ScarabUserManager.getInstance(userName);
+ HashMap values = getAttributes(issue);
+ ActivitySet activitySet = null;
+ Attachment reason = new Attachment();
+ reason.setData("Created by xmlrpc");
+ reason.setName("reason");
+ activitySet = issue
+ .setInitialAttributeValues(activitySet, reason, values, user);
+
+// issue.setAttributeValues(activitySet, values, reason, user);
+ // Save any unsaved attachments as part of this ActivitySet as well
+ setAttributes(issue,activitySet,reason,user,attribs);
+ activitySet = issue.doSaveFileAttachments(activitySet, user);
+ activitySet.save();
+ return issue.getUniqueId();
+ }
+
+ /**
+ * @param attribs
+ * @return
+ * @throws TorqueException
+ * @throws ScarabException
+ */
+ private void setAttributes(Issue issue, ActivitySet activitySet, Attachment attachment, ScarabUser user, Hashtable attribs) throws TorqueException, ScarabException {
+
+ LinkedMap avMap = issue.getModuleAttributeValuesMap(false);
+ HashMap newValues = new HashMap();
+
+ for (MapIterator i = avMap.mapIterator();i.hasNext();)
+ {
+ AttributeValue aval = (AttributeValue)avMap.get(i.next());
+ String key = aval.getAttribute().getName();
+ String newValue = (String)attribs.get(key);
+
+ if (newValue != null) {
+ AttributeValue newVal = aval.copy();
+ newVal.setValue(newValue);
+ newValues.put(aval.getAttributeId(),newVal);
+ }
+ }
+ issue.setAttributeValues(activitySet, newValues, attachment, user);
+ }
+
+ /**
+ * @param attribs
+ * @return
+ * @throws TorqueException
+ */
+ private HashMap getAttributes(Issue issue) throws TorqueException {
+
+
+ return new HashMap(issue.getAttributeValuesMap());
+ }
+
+ /**
+ * @param module
+ * @throws TorqueException
+ */
+ private Module getModuleByCode(String module) throws TorqueException {
+ final Criteria crit = new Criteria();
+ if( module != null )
+ {
+ crit.add(ScarabModulePeer.MODULE_CODE, module);
+ }
+ final List result = ScarabModulePeer.doSelect(crit);
+ if (result.size() != 1)
+ {
+ throw new TorqueException ("Selected: " + result.size() +
+ " rows. Expected 1."); //EXCEPTION
+ }
+ return (Module) result.get(0); // TODO Auto-generated method stub
+
+ }
+
+}
Added: trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateException.java
Url: http://scarab.tigris.org/source/browse/scarab/trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateException.java?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateException.java 2006-10-18 16:51:29-0700
@@ -0,0 +1,9 @@
+package org.tigris.scarab.xmlrpc;
+
+public class ScarabUpdateException extends Exception {
+
+ public ScarabUpdateException(String string) {
+ super(string);
+ }
+
+}
Added: trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHandler.java
Url: http://scarab.tigris.org/source/browse/scarab/trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHandler.java?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHandler.java 2006-10-18 16:51:29-0700
@@ -0,0 +1,78 @@
+package org.tigris.scarab.xmlrpc;
+
+import org.apache.log4j.Logger;
+
+import java.util.Vector;
+
+import org.apache.torque.TorqueException;
+
+public class ScarabUpdateHandler {
+ /**
+ * Logger for this class
+ */
+ private static final Logger log = Logger
+ .getLogger(ScarabUpdateHandler.class);
+
+
+ public Vector addAttributeOption(String attr, String opt)
+ {
+ ScarabUpdateHelper helper = new ScarabUpdateHelper();
+
+ Vector rslt = new Vector();
+
+ try {
+ helper.addAttributeOption(attr, opt);
+ helper.mapAttributeOptionToAllModuleIssueTypes(attr, opt);
+ rslt.add(new Boolean(true));
+ rslt.add("OK");
+ } catch (Exception e) {
+ log.error("addAttributeOption(String, String) - : attr=" + attr
+ + ", opt=" + opt + ", rslt=" + rslt, e);
+
+ rslt.add(new Boolean(false));
+ rslt.add("Exception : " + e);
+ }
+ return rslt;
+ }
+
+ public Vector updateAttributeOptions(String attr, Vector options)
+ {
+ ScarabUpdateHelper helper = new ScarabUpdateHelper();
+
+ Vector rslt = new Vector();
+
+ try {
+ helper.updateAttributeOptions(attr, options);
+ rslt.add(new Boolean(true));
+ rslt.add("OK");
+ } catch (Exception e) {
+ log.error("updateAttributeOption(String, Vector) - : attr=" + attr
+ + ", options=" + options + ", rslt=" + rslt, e);
+
+ rslt.add(new Boolean(false));
+ rslt.add("Exception : " + e);
+ }
+ return rslt;
+ }
+
+ public Vector sortAttributeOptions(String attr)
+ {
+ ScarabUpdateHelper helper = new ScarabUpdateHelper();
+
+ Vector rslt = new Vector();
+
+ try {
+ helper.sortAttributeOptions(attr);
+ rslt.add(new Boolean(true));
+ rslt.add("OK");
+ } catch (Exception e) {
+ log.error("updateAttributeOption(String, Vector) - : attr=" + attr
+ + ", rslt=" + rslt, e);
+
+ rslt.add(new Boolean(false));
+ rslt.add("Exception : " + e);
+ }
+ return rslt;
+ }
+
+}
Added: trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHelper.java
Url: http://scarab.tigris.org/source/browse/scarab/trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHelper.java?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/src/java/org/tigris/scarab/xmlrpc/ScarabUpdateHelper.java 2006-10-18 16:51:29-0700
@@ -0,0 +1,576 @@
+package org.tigris.scarab.xmlrpc;
+
+import org.apache.log4j.Logger;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.torque.NoRowsException;
+import org.apache.torque.TooManyRowsException;
+import org.apache.torque.TorqueException;
+import org.apache.torque.util.Criteria;
+import org.tigris.scarab.om.Attribute;
+import org.tigris.scarab.om.AttributeOption;
+import org.tigris.scarab.om.AttributeOptionManager;
+import org.tigris.scarab.om.AttributeOptionPeer;
+import org.tigris.scarab.om.AttributePeer;
+import org.tigris.scarab.om.IssueType;
+import org.tigris.scarab.om.IssueTypePeer;
+import org.tigris.scarab.om.Module;
+import org.tigris.scarab.om.ParentChildAttributeOption;
+import org.tigris.scarab.om.RIssueTypeAttributePeer;
+import org.tigris.scarab.om.RIssueTypeOption;
+import org.tigris.scarab.om.RModuleAttribute;
+import org.tigris.scarab.om.RModuleAttributePeer;
+import org.tigris.scarab.om.RModuleOption;
+
+public class ScarabUpdateHelper {
+ /**
+ * Internal and external state data
+ *
+ * @author pti
+ *
+ */
+ private class SyncState {
+ /**
+ * Logger for this class
+ */
+ private final Logger log = Logger.getLogger(SyncState.class);
+
+ /**
+ * Logger for this class
+ */
+ private boolean external = false;
+
+ private boolean internal = false;
+
+ private SyncState() {
+
+ }
+
+ /**
+ * @return Returns the external.
+ */
+ public boolean isExternal() {
+ return external;
+ }
+
+ /**
+ * @return Returns the internal.
+ */
+ public boolean isInternal() {
+ return internal;
+ }
+
+ /**
+ * @param external
+ * The external to set.
+ */
+ public void setExternal(boolean external) {
+ this.external = external;
+ }
+
+ /**
+ * @param internal
+ * The internal to set.
+ */
+ public void setInternal(boolean internal) {
+ this.internal = internal;
+ }
+ }
+
+ /**
+ * The SyncStateMap keeps a map which reflects the difference between the
+ * internal and external lists.
+ *
+ * @author pti
+ *
+ */
+ private class SyncStateMap {
+ /**
+ * Logger for this class
+ */
+ private final Logger log = Logger.getLogger(SyncStateMap.class);
+
+ private Map hash = new HashMap();
+
+ /**
+ * Logger for this class
+ */
+ public SyncState getSyncState(String s) {
+ SyncState rslt = (SyncState) hash.get(s);
+ if (rslt == null) {
+ rslt = new SyncState();
+ hash.put(s, rslt);
+ }
+ return rslt;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#keySet()
+ */
+ public Set keySet() {
+ return hash.keySet();
+ }
+
+ }
+
+ /**
+ * Logger for this class
+ */
+ private static final Logger log = Logger
+ .getLogger(ScarabUpdateHelper.class);
+
+ /**
+ * Add the option with the given name to the attribute with the given name
+ *
+ * @param attribute
+ * a String with the global attribute name
+ * @param optionName
+ * a String with the name of the option
+ * @throws Exception
+ */
+ public void addAttributeOption(String attribute, String optionName)
+ throws Exception {
+ // get the relevant entities
+ AttributeOption option = getAttributeOption(attribute, optionName);
+ Attribute attr = getOptionAttribute(attribute);
+ setAttributeOptionDeletedFlag(attr, option, false);
+ }
+
+ /**
+ * Remove option from global attribute
+ *
+ * @param attribute
+ * @param optionName
+ * @throws Exception
+ */
+ public void removeAttributeOption(String attribute, String optionName)
+ throws Exception {
+
+ // get the relevant entities
+ AttributeOption option = getAttributeOption(attribute, optionName);
+ Attribute attr = getOptionAttribute(attribute);
+
+ setAttributeOptionDeletedFlag(attr, option, true);
+ option.deleteIssueTypeMappings();
+ option.deleteModuleMappings();
+
+ }
+
+
+ /**
+ * Finds and returns the Attribute object having the name given as
+ * parameter.
+ *
+ * @param attribute
+ * a String with the name of the Global Attribute
+ * @return
+ * @throws ScarabUpdateException
+ * @throws TorqueException
+ */
+ private Attribute getAttribute(String attribute)
+ throws ScarabUpdateException, TorqueException {
+ // find the corresponding attribute
+ Attribute attr;
+ Criteria crit = new Criteria();
+ crit.add(AttributePeer.ATTRIBUTE_NAME, (Object) attribute,
+ Criteria.EQUAL);
+ List attrList = AttributePeer.doSelect(crit);
+ if (attrList.size() == 1) {
+ attr = (Attribute) attrList.get(0);
+ } else {
+ throw new ScarabUpdateException(
+ "Found " + attrList.size() + " attributes for " + attribute + ".");
+ }
+ return attr;
+ }
+
+ /**
+ * Return the Atribute and tests if it is an option attribute. Throws an
+ * exception if an attribute was found, but it is not of an option type.
+ *
+ * @param attribute
+ * @return
+ * @throws ScarabUpdateException
+ * @throws TorqueException
+ */
+ private Attribute getOptionAttribute(String attribute)
+ throws ScarabUpdateException, TorqueException {
+ Attribute attr = getAttribute(attribute);
+ if (attr.isOptionAttribute()) {
+ // do nothing
+ } else {
+ throw new ScarabUpdateException(
+ "Found non-optiontype attribute when an option type attribte was expected.");
+ }
+ return attr;
+ }
+
+ /**
+ * Return the AttributeOption object with the combination of attrobute and
+ * option given as their user visible names.
+ *
+ * @param attribute
+ * The name of the global attribute
+ * @param option
+ * The name of the option in the global attribute
+ * @return the AttributeOption with the above combination
+ * @throws TorqueException
+ * @throws ScarabUpdateException
+ */
+ private AttributeOption getAttributeOption(String attribute, String option)
+ throws Exception {
+ // get all options related to this attribute
+ Attribute attr = getOptionAttribute(attribute);
+ return AttributeOptionManager.getInstance(attr, option);
+ }
+
+ /**
+ * Find the list of possible attributes for an attribute specified by its
+ * global name
+ *
+ * @param attribute
+ * @return A list of strings containing the options available for the
+ * specified attribute
+ * @throws TorqueException
+ * @throws ScarabUpdateException
+ */
+ public List getAttributeOptions(String attribute) throws TorqueException,
+ ScarabUpdateException {
+ Criteria crit = new Criteria();
+
+ // get all non-deleted options for this attribute
+ crit.add(AttributePeer.ATTRIBUTE_NAME, (Object) attribute,
+ Criteria.EQUAL);
+ crit.add(AttributeOptionPeer.DELETED, false);
+ crit.addJoin(AttributePeer.ATTRIBUTE_ID,
+ AttributeOptionPeer.ATTRIBUTE_ID);
+ return AttributeOptionPeer.doSelect(crit);
+ }
+
+ /**
+ * Find the list of Module/Issuetype combinations using the given attribute
+ *
+ * @param attribute
+ * @return A list of RModuleAttributes related to this attribute.
+ * @throws TorqueException
+ * @throws ScarabUpdateException
+ */
+ private List getModuleAttributes(String attribute) throws TorqueException,
+ ScarabUpdateException {
+ Criteria crit = new Criteria();
+
+ // get all non-deleted options for this attribute
+ crit.add(AttributePeer.ATTRIBUTE_NAME, (Object) attribute,
+ Criteria.EQUAL);
+ crit.addJoin(AttributePeer.ATTRIBUTE_ID,
+ RModuleAttributePeer.ATTRIBUTE_ID);
+ List matOptions = RModuleAttributePeer.doSelect(crit);
+
+ return matOptions;
+ }
+
+ /**
+ * Find the list of Module/Issuetype combinations using the given attribute
+ *
+ * @param attribute
+ * @return A list of RModuleAttributes related to this attribute.
+ * @throws TorqueException
+ * @throws ScarabUpdateException
+ */
+ private List getIssueTypes(String attribute) throws TorqueException,
+ ScarabUpdateException {
+ Criteria crit = new Criteria();
+
+ // get all non-deleted options for this attribute
+ crit.add(AttributePeer.ATTRIBUTE_NAME, (Object) attribute,
+ Criteria.EQUAL);
+ crit.addJoin(AttributePeer.ATTRIBUTE_ID,
+ RIssueTypeAttributePeer.ATTRIBUTE_ID);
+ crit.addJoin(IssueTypePeer.ISSUE_TYPE_ID,
+ RIssueTypeAttributePeer.ISSUE_TYPE_ID);
+ List itOptions = IssueTypePeer.doSelect(crit);
+
+ return itOptions;
+ }
+
+ /**
+ * Map an option to all Module/Issuetypes which have this attribute
+ * configured. In case the option is deleted, all mappings are removed.
+ *
+ * @throws Exception
+ *
+ */
+ public void mapAttributeOptionToAllModuleIssueTypes(String attribute,
+ String option) throws Exception {
+ Iterator iter;
+ AttributeOption ao = getAttributeOption(attribute, option);
+ if (ao.getDeleted()) {
+ ao.deleteIssueTypeMappings();
+ ao.deleteModuleMappings();
+ } else {
+ // add module mappings
+ iter = getModuleAttributes(attribute).iterator();
+ while (iter.hasNext()) {
+ RModuleAttribute rat = (RModuleAttribute) iter.next();
+ IssueType it = rat.getIssueType();
+ Module mod = rat.getModule();
+
+ // skip template IssueTypes
+ if (!isIssueTypeTemplate(it) &&
+ (mod.getRModuleOption(ao,it) == null)) {
+ if (log.isDebugEnabled()) {
+ log.debug("mapAttributeOptionToAllModuleIssueTypes(String, String) - Adding attribute to module/issuetype : ao=" + ao + ", rat=" + rat + ", it=" + it + ", mod=" + mod); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ mod.addAttributeOption(it,ao);
+ }
+ }
+
+ // add issuetype mappings
+ iter = getIssueTypes(attribute).iterator();
+ while (iter.hasNext()) {
+ IssueType it = (IssueType) iter.next();
+ try {
+ it.addRIssueTypeOption(ao);
+ } catch (TorqueException e) {
+ if (log.isEnabledFor(org.apache.log4j.Priority.WARN)) {
+ log
+ .warn(
+ "mapAttributeOptionToAllModuleIssueTypes(String, String) - IssueTypeMApping alreadyt exists for this combination : attribute=" + attribute + ", option=" + option + ", it=" + it, null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * Sort options in all Module/Issuetypes which have this attribute
+ * configured. In case the option is deleted, all mappings are removed.
+ *
+ * @throws Exception
+ *
+ */
+ public void sortAttributeOptions(String attribute) throws Exception {
+ Iterator iter;
+
+ /**
+ * Compare the order of 2 RModuleOptions based on their display value
+ *
+ * @author pti
+ *
+ */
+ final class RMOComparator implements Comparator {
+ /**
+ * Logger for this class
+ */
+ private final Logger log = Logger.getLogger(RMOComparator.class);
+
+ public int compare(Object arg0, Object arg1) {
+ // TODO Auto-generated method stub
+ String s1 = ((RModuleOption)arg0).getDisplayValue();
+ String s2 = ((RModuleOption)arg1).getDisplayValue();
+
+ return s1.compareTo(s2);
+ }
+
+ };
+ Comparator rmocomp = new RMOComparator();
+
+
+ /**
+ * Compare the order of 2 RIssueTypeOptions based on their display value
+ *
+ * @author pti
+ *
+ */
+ final class RIOComparator implements Comparator {
+ /**
+ * Logger for this class
+ */
+ private final Logger log = Logger.getLogger(RIOComparator.class);
+
+ public int compare(Object arg0, Object arg1) {
+ Integer oid1 = ((RIssueTypeOption)arg0).getOptionId();
+ Integer oid2 = ((RIssueTypeOption)arg1).getOptionId();
+ AttributeOption ao1=null;
+ AttributeOption ao2=null;
+ try {
+ ao1 = AttributeOptionPeer.retrieveByPK(oid1);
+ ao2 = AttributeOptionPeer.retrieveByPK(oid2);
+ } catch (NoRowsException e) {
+ log.error("No Rows exception when sorting IssueTypeOptions.",e);
+ } catch (TooManyRowsException e) {
+ log.error("Too many rows when sorting IssueTypeOptions.",e);
+ } catch (TorqueException e) {
+ log.error("Torque exception when sorting IssueTypeOptions.",e);
+ }
+
+ return ao1.getName().compareTo(ao2.getName());
+ }
+
+ };
+ Comparator riocomp = new RIOComparator();
+
+ /**
+ * Compare the order of 2 ParentChildAttributeOptions based on their display value
+ *
+ * @author pti
+ *
+ */
+ final class PCAOComparator implements Comparator {
+ /**
+ * Logger for this class
+ */
+ private final Logger log = Logger.getLogger(PCAOComparator.class);
+
+ public int compare(Object arg0, Object arg1) {
+ // TODO Auto-generated method stub
+ String s1 = ((ParentChildAttributeOption)arg0).getName();
+ String s2 = ((ParentChildAttributeOption)arg1).getName();
+
+ return s1.compareTo(s2);
+ }
+
+ };
+ Comparator pcaocomp = new PCAOComparator();
+
+
+ Attribute attr = getAttribute(attribute);
+
+ // sort module mappings
+ iter = getModuleAttributes(attribute).iterator();
+ while (iter.hasNext()) {
+ RModuleAttribute rat = (RModuleAttribute) iter.next();
+ IssueType it = rat.getIssueType();
+ Module mod = rat.getModule();
+
+ // skip template IssueTypes
+ if (!isIssueTypeTemplate(it)) {
+ List options = mod.getRModuleOptions(attr,it);
+ Collections.sort(options, rmocomp);
+ for (int i = 0; i < options.size(); i++) {
+ RModuleOption opt = (RModuleOption)options.get(i);
+ opt.setOrder(i + 1);
+ opt.save();
+ }
+
+ }
+ }
+
+ // add issuetype mappings
+ iter = getIssueTypes(attribute).iterator();
+ while (iter.hasNext()) {
+ IssueType it = (IssueType) iter.next();
+ List options = it.getRIssueTypeOptions(attr);
+ Collections.sort(options, riocomp);
+ for (int i = 0; i < options.size(); i++) {
+ RIssueTypeOption opt = (RIssueTypeOption)options.get(i);
+ opt.setOrder(i + 1);
+ opt.save();
+ }
+
+ }
+
+ // sort pcao mappings
+ List pcaos = attr.getParentChildAttributeOptions();
+ Collections.sort(pcaos, pcaocomp);
+ for (int i = 0; i < pcaos.size(); i++) {
+ ParentChildAttributeOption opt = (ParentChildAttributeOption)pcaos.get(i);
+ opt.setPreferredOrder(i + 1);
+ opt.save();
+ }
+
+ }
+
+ private boolean isIssueTypeTemplate(IssueType it) {
+ return it.getParentId().intValue() != 0;
+ }
+
+ /**
+ * Handle the the setting, clearing of the deleted flag. If the record does
+ * not exist, it is created.
+ *
+ * @param attribute
+ * @param optionName
+ * @param deleted
+ * @throws Exception
+ */
+ private void setAttributeOptionDeletedFlag(Attribute attr,
+ AttributeOption option, boolean deleted) throws Exception {
+ ParentChildAttributeOption pcao = ParentChildAttributeOption
+ .getInstance();
+
+ // claculate some sensible defaults
+ int order = attr.getAttributeOptions().size() + 1;
+ int weight = order;
+
+ // populate the object and save it to db
+ pcao.setAttributeId(attr.getAttributeId());
+ pcao.setOptionId(option != null ? option.getOptionId() : null);
+ pcao.setParentId(new Integer(0));
+ pcao.setName(option.getName());
+ pcao.setPreferredOrder(order);
+ pcao.setWeight(weight);
+ pcao.setDeleted(deleted);
+ pcao.save();
+
+ }
+
+ /**
+ * Set the option list for the specified global attribute to correspond to
+ * the list of strings given. In order to preserve setting changes throught
+ * the user interface, only minimal changes are done.
+ *
+ * @param attribute
+ * The name of the global attribute
+ * @param options
+ * The list of strings of the new attributes.
+ * @throws Exception
+ */
+ public void updateAttributeOptions(String attribute, List options)
+ throws Exception {
+ SyncStateMap ssm = new SyncStateMap();
+ Iterator iter = null;
+
+ // Retrieve attribute to be updated
+ iter = getAttributeOptions(attribute).iterator();
+ while (iter.hasNext()) {
+
+ ssm.getSyncState(((AttributeOption)iter.next()).getName()).setInternal(true);
+ }
+
+ // create SyncState info based on given external list.
+ iter = options.iterator();
+ while (iter.hasNext()) {
+ String option = (String) iter.next();
+ ssm.getSyncState(option).setExternal(true);
+ }
+
+ // iterate over the syncstates
+ iter = options.iterator();
+ while (iter.hasNext()) {
+ String key = (String) iter.next();
+ SyncState ss = ssm.getSyncState(key);
+ if (ss.isExternal() && !ss.isInternal()) {
+ // new in external data : add to attribute options
+ addAttributeOption(attribute, key);
+ mapAttributeOptionToAllModuleIssueTypes(attribute, key);
+ } else if (!ss.isExternal() && ss.isInternal()) {
+ // no longer in external data : remove from option list
+ removeAttributeOption(attribute, key);
+ mapAttributeOptionToAllModuleIssueTypes(attribute, key);
+ }
+ }
+ }
+
+}
Added: trunk/src/test/org/tigris/scarab/xmlrpc/NewTicketHandlerTest.java
Url: http://scarab.tigris.org/source/browse/scarab/trunk/src/test/org/tigris/scarab/xmlrpc/NewTicketHandlerTest.java?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/src/test/org/tigris/scarab/xmlrpc/NewTicketHandlerTest.java 2006-10-18 16:51:29-0700
@@ -0,0 +1,100 @@
+/**
+ *
+ */
+package org.tigris.scarab.xmlrpc;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.torque.TorqueException;
+import org.tigris.scarab.om.AttributeValue;
+import org.tigris.scarab.om.Issue;
+import org.tigris.scarab.om.IssueManager;
+import org.tigris.scarab.test.BaseScarabTestCase;
+import org.tigris.scarab.util.ScarabException;
+
+import junit.framework.TestCase;
+
+/**
+ * @author pti
+ *
+ */
+public class NewTicketHandlerTest extends BaseScarabTestCase {
+
+ NewTicketHandler nth;
+ SimpleHandler sh;
+ private String module;
+ private String user;
+ private Hashtable attribs;
+ private String issueType;
+ private Integer statusId;
+ private Integer newId;
+
+ /**
+ * @param arg0
+ * @throws Exception
+ */
+ public NewTicketHandlerTest(String arg0) throws Exception {
+ super(arg0);
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ nth = new NewTicketHandler();
+ sh = new SimpleHandler();
+
+ module = "PAC";
+ issueType = "Defect";
+ user = "[email protected]";
+ attribs = new Hashtable();
+ statusId = new Integer(3);
+ newId = new Integer(2);
+
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testNewTicketTest() throws ScarabException, TorqueException {
+ String ticketId = nth.createNewTicket(module, issueType, user, attribs);
+ assertNotNull("TicketId should not be null", ticketId);
+ assertTrue(ticketId.matches("^PAC[0-9]+"));
+ Issue issue = IssueManager.getIssueById(ticketId);
+ assertNotNull(issue);
+ }
+
+ public void testNewTicketChangeAttributeOption() throws Exception {
+
+ // Known attributes are mapped to their text representation
+ attribs.put("Status","New");
+ attribs.put("Description","Some interesting description.");
+ attribs.put("AssignedTo", user);
+
+ // unknown attributes are silently discarded.
+ attribs.put("Brol", user);
+ String ticketId = nth.createNewTicket(module, issueType, user, attribs);
+
+ Issue issue = IssueManager.getIssueById(ticketId);
+
+ AttributeValue attributeValue;
+ attributeValue = issue.getAttributeValue("Status");
+ assertEquals("New",attributeValue.getValue());
+ attributeValue = issue.getAttributeValue("Description");
+ assertEquals("Some interesting description.",attributeValue.getValue());
+ attributeValue = issue.getAttributeValue("AssignedTo");
+ assertEquals(user,attributeValue.getValue());
+
+ attributeValue = issue.getAttributeValue("Brol");
+ assertNull(attributeValue);
+
+ }
+}
Added: trunk/xdocs/howto/ldap-howto.xml
Url: http://scarab.tigris.org/source/browse/scarab/trunk/xdocs/howto/ldap-howto.xml?view=auto&rev=10311
==============================================================================
--- (empty file)
+++ trunk/xdocs/howto/ldap-howto.xml 2006-10-18 16:51:29-0700
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+
+<document>
+
+<properties>
+ <title>How to authenticate Scarab against an LDAP server</title>
+ <author>Peter Tillemans</author>
+</properties>
+
+<body>
+
+
+<section name="Enabling LDAP Authentication">
+<p>
+Since B21, Scarab is able to authenticate users against an LDAP server. To do
+this we use the JAAS authentication method to authenticate the username/password
+pair and a new security service which is derived from the standard database
+based ScarabDBSecurityService.
+</p>
+<p>
+The principle is to create the user <strong>on the fly</strong> in case the user
+does not exist yet. Relevant data (full name and email address) are copied from
+the LDAP server. In the case we cannot authenticate against the LDAP server the
+normal database based authentication takes place. This means that users who have
+no LDAP account can be created in the 'traditonal' way. This is very useful for
+occasional or temporary workers who need access to the Scarab but are not
+employees of the company.
+</p>
+<p>
+To enable LDAP authentication, tomcat must be told where to find the jaas
+configuration file. The easy way is to define JAVA_OPTS to contain the
+path to it. I recommend a startup script in the tomcat directory of scarab to
+set the environment variable and launch the regular startup script.
+</p>
+
+<p>Here is a Un*x example for bash or family.</p>
+
+<pre>
+#!/bin/bash
+#
+# Start catalina with the options to find the ldap server
+#
+export JAVA_OPTS="-Djava.security.auth.login.config=`pwd`/../src/webapp/WEB-INF/conf/jaas.conf"
+
+echo JAVA_OPTS=${JAVA_OPTS}
+
+bin/catalina.sh $*
+</pre>
+
+<p>
+Now create a <strong>jaas.conf</strong> file in the location pointer to by the
+<strong>java.security.auth.login.config</strong> property. Here is an example:
+</p>
+
+<pre>
+Scarab {
+ org.tigris.scarab.services.security.ldap.LdapLoginModule required host="ldap://ldap1.tess.elex.be:389" usernamefield="uid" basedn="dc=elex";
+};
+</pre>
+
+<p>Now we must swap the standard ScarabDBSecurityService with the replacement ScarabLDAPDBSecurityService. Open
+the TurbineResource.properties and :</p>
+
+
+<pre>
+...
+# -------------------------------------------------------------------
+#
+# S E R V I C E S
+#
+# -------------------------------------------------------------------
+# Classes for Turbine Services should be defined here.
+# Format: services.[name].classname=[implementing class]
+#
+# To specify properties of a service use the following syntax:
+# service.[name].[property]=[value]
+#
+# The order that these services is listed is important! The
+# order that is stated here is the order in which the services
+# will be initialized. Keep this is mind if you have services
+# that depend on other services during initialization.
+# -------------------------------------------------------------------
+services.YaafiComponentService.classname=org.apache.turbine.services.yaaficomponent.TurbineYaafiComponentService
+services.SecurityService.classname=<strong>org.tigris.scarab.services.security.ScarabLDAPDBSecurityService</strong>
+services.TemplateService.classname=org.apache.fulcrum.template.TurbineTemplateService
+
+services.RunDataService.classname=org.apache.turbine.services.rundata.TurbineRunDataService
+services.PullService.classname=org.apache.turbine.services.pull.TurbinePullService
+services.IntakeService.classname=org.apache.fulcrum.intake.TurbineIntakeService
+...
+</pre>
+</section>
+<section name="LDAP Synchronization">
+<p>
+In an enterprise setting it is useful that the users are actually created
+before they log in the first time. The Scarab administrators are then able
+to provide new team members with the needed roles and access rights before
+they login. This helps with the adoption in a larger setting and reduces
+the load on the service desk.
+</p>
+<p>
+To provide this feature the LDAP SecurityService can synchronize the accounts
+on startup. However some care must be taken to limit the number of accounts
+to be provisioned in this way limited to a couple of thousand users to keep
+the startup time within reason. On the machines this was tested there id an
+additional second startup time for every 100 users coming from an LDAP server
+on the LAN.
+</p>
+<p>
+In the build.properties file, include the following properties:
+</p>
+<pre>
+ # enable the ldap synchronization
+ scarab.login.ldap.synchronizeOnStartup=true
+
+ # the following parameters are only used
+ # when synchronization is enabled
+ scarab.login.ldap.providerFactory=com.sun.jndi.ldap.LdapCtxFactory
+ scarab.login.ldap.providerUrl=ldap://localhost/
+ scarab.login.ldap.ldapQuery=(objectClass=posixAccount)
+ scarab.login.ldap.baseDn=dc=example,dc=com
+ scarab.login.ldap.loginAttribute=uid
+</pre>
+<p>
+Normally there is no need to change the provider factory. The providerUrl must
+point to the servername (and port in case it runs on another port) of your LDAP
+server. The ldapQuery is intended to avoid pulling in too many LDAP objects which
+might cause security holes because of the weak password on dummy accounts. The
+baseDn points to the start position in the tree from where the recursive search
+using the ldapQuery starts. Since different schemas use different attributes to
+map the login name too, this can be defined using the loginAttribute property.
+</p>
+</section>
+<section name="Limitations">
+<p>
+During the synchronisation the contents of the Scarab database account fields is
+overwritten with the values of the relevant fields of the LDAP account object.
+This has the advantage that the Scarab database always reflect the LDAP contents,
+but it has the disadvantage that it is not possible that somebody uses a different
+email for Scarab mails than the one defined in LDAP.
+</p>
+<p>
+In a company setting this kind of flexibility is probably unwanted anyway since
+if an LDAP has been setup, then probably it is (derived of) the <strong>Single
+Point Of Truth</strong>. This approach follows according to me the
+<strong>Principle of Leas Astonishment</strong> : it is simple, predictable,
+stable over time and will probably generate the least amount of user frustration
+and helpdesk tickets this way.
+</p>
+<p>
+However to be consequent, the user account form should know and shoe that the
+LDAP derived attributes should be treated as read-only. This is currently not
+the case. Idem dito for password recovery. (Probably fixing this UI issue will
+silence the voices clamoring for the freedom to mess up their account data ;-).
+</p>
+<p>
+More serious is the fact that the synchronizer does not disable accounts which
+have been removed or disabled in the LDAP. If security matters to you, you might
+consider to not copy the password to the database in the
+<strong>org.tigris.scarab.services.security.ScarabLDAPDBSecurityService</strong>.
+Alternatively to scramble the password during synchronisation in
+<strong>org.tigris.scarab.services.security.ldap.LDAPSynchronizer.java</strong>.
+(Maybe this should be the behavior by default?)</p>
+<p>
+Some more limitations (nice-to-haves in my eyes) are :
+<ul>
+ <li>Module roles tied to LDAP groups.</li>
+ <li>Scheduled synchronisation</li>
+ <li>Asynchronous realtime replication from the LDAP server</li>
+ <li>optional disabling of authentication against the DB</li>
+ <li>might be more logical to place the authentication code in a valve</li>
+ <li>might be more logical to place the synchronizer as a separate service.</li>
+</ul>
+</p>
+</section>
+</body>
+</document>
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.