001: package com.technoetic.xplanner.soap;
002:
003: import java.io.Serializable;
004: import java.lang.reflect.Array;
005: import java.lang.reflect.InvocationTargetException;
006: import java.lang.reflect.Method;
007: import java.util.*;
008:
009: import net.sf.hibernate.Hibernate;
010: import net.sf.hibernate.HibernateException;
011: import net.sf.hibernate.ObjectNotFoundException;
012: import net.sf.hibernate.Session;
013: import net.sf.hibernate.Transaction;
014: import net.sf.hibernate.type.Type;
015: import org.apache.commons.beanutils.BeanUtils;
016: import org.apache.commons.beanutils.ConvertUtils;
017: import org.apache.commons.beanutils.Converter;
018: import org.apache.commons.beanutils.PropertyUtils;
019: import org.apache.commons.beanutils.converters.ByteArrayConverter;
020: import org.apache.log4j.Logger;
021:
022: import com.technoetic.xplanner.db.QueryException;
023: import com.technoetic.xplanner.db.TaskQueryHelper;
024: import com.technoetic.xplanner.db.TaskQuery;
025: import com.technoetic.xplanner.db.hibernate.ThreadSession;
026: import com.technoetic.xplanner.domain.*;
027: import com.technoetic.xplanner.domain.repository.AttributeRepository;
028: import com.technoetic.xplanner.domain.repository.AttributeRepositoryImpl;
029: import com.technoetic.xplanner.filters.ThreadServletRequest;
030: import com.technoetic.xplanner.history.HistoricalEvent;
031: import com.technoetic.xplanner.history.HistorySupport;
032: import com.technoetic.xplanner.security.AuthenticationException;
033: import com.technoetic.xplanner.security.SecurityHelper;
034: import com.technoetic.xplanner.security.auth.SystemAuthorizer;
035: import com.technoetic.xplanner.soap.domain.*;
036: import com.technoetic.xplanner.tags.DomainContext;
037: import com.technoetic.xplanner.util.MainBeanFactory;
038:
039: // TODO: SOAP input validation.
040: // Ideally, extract the validation out of the struts forms into reusable
041: // validation to be used by soap
042:
043: public class XPlanner {
044: private Logger log = Logger.getLogger(getClass());
045: private AttributeRepository attributes = new AttributeRepositoryImpl();
046:
047: public XPlanner() {
048: // The SOAP interface is required to use Calendars for dates. This
049: // converter is intended to be an adapter for the Date usage in the
050: // XPlanner domain objects. However, I'm not comfortable with this since
051: // the converters are global objects.
052: ConvertUtils.register(new Converter() {
053: public Object convert(Class type, Object value) {
054: if (value == null)
055: return null;
056: if (value instanceof Calendar)
057: return ((Calendar) value).getTime();
058: return value;
059: }
060: }, Date.class);
061: ConvertUtils.register(new Converter() {
062: public Object convert(Class type, Object value) {
063: if (value == null)
064: return null;
065: if (value instanceof Date) {
066: Calendar calendar = Calendar.getInstance();
067: calendar.setTime((Date) value);
068: return calendar;
069: }
070: return value;
071: }
072: }, Calendar.class);
073: ConvertUtils.register(new ByteArrayConverter(null),
074: byte[].class); // by default a null value is not converted in a null array.
075: }
076:
077: //
078: // Projects
079: //
080:
081: public ProjectData[] getProjects() throws Exception {
082: return (ProjectData[]) getObjects(ProjectData.class, null,
083: null, null, null);
084: }
085:
086: public ProjectData getProject(int id) throws Exception {
087: return (ProjectData) getObject(ProjectData.class, id);
088: }
089:
090: public ProjectData addProject(ProjectData project) throws Exception {
091: return (ProjectData) addObject(0, project);
092: }
093:
094: public void removeProject(int id) throws Exception {
095: removeObject(ProjectData.class, id);
096: }
097:
098: public void update(ProjectData object) throws Exception {
099: updateObject(object);
100: }
101:
102: public IterationData getCurrentIteration(int projectId)
103: throws Exception {
104: IterationData[] iterations = (IterationData[]) getObjects(
105: IterationData.class,
106: "object.startDate <= ? and object.endDate >= ? and object.projectId = ?",
107: new Object[] { new Date(), new Date(),
108: new Integer(projectId) }, new Type[] {
109: Hibernate.DATE, Hibernate.DATE,
110: Hibernate.INTEGER }, null);
111: return iterations.length > 0 ? iterations[0] : null;
112: }
113:
114: public IterationData[] getIterations(int projectId)
115: throws Exception {
116: return (IterationData[]) getObjects(ProjectData.class,
117: projectId, "iterations", IterationData.class);
118: }
119:
120: //
121: // Iterations
122: //
123:
124: public IterationData getIteration(int id) throws Exception {
125: return (IterationData) getObject(IterationData.class, id);
126: }
127:
128: public IterationData addIteration(IterationData iteration)
129: throws Exception {
130: return (IterationData) addObject(getProjectId(Project.class,
131: iteration.getProjectId()), iteration);
132: }
133:
134: public void removeIteration(int id) throws Exception {
135: removeObject(IterationData.class, id);
136: }
137:
138: public void update(IterationData object) throws Exception {
139: updateObject(object);
140: }
141:
142: public UserStoryData[] getUserStories(int containerId)
143: throws Exception {
144: return (UserStoryData[]) getObjects(IterationData.class,
145: containerId, "userStories", UserStoryData.class);
146: }
147:
148: //
149: // User Stories
150: //
151:
152: public UserStoryData getUserStory(int id) throws Exception {
153: return (UserStoryData) getObject(UserStoryData.class, id);
154: }
155:
156: public UserStoryData addUserStory(UserStoryData story)
157: throws Exception {
158: return (UserStoryData) addObject(getProjectId(Iteration.class,
159: story.getIterationId()), story);
160: }
161:
162: public void removeUserStory(int id) throws Exception {
163: removeObject(UserStoryData.class, id);
164: }
165:
166: public void update(UserStoryData object) throws Exception {
167: updateObject(object);
168: }
169:
170: public TaskData[] getTasks(int containerId) throws Exception {
171: return (TaskData[]) getObjects(UserStoryData.class,
172: containerId, "tasks", TaskData.class);
173: }
174:
175: //FEATURE:
176: // public FeatureData[] getFeatures(int containerId) throws Exception {
177: // return (FeatureData[])getObjects(UserStoryData.class, containerId, "features", FeatureData.class);
178: // }
179:
180: //
181: // Features
182: //
183:
184: // public FeatureData getFeature(int id) throws Exception {
185: // return (FeatureData)getObject(FeatureData.class, id);
186: // }
187: //
188: // public FeatureData addFeature(FeatureData feature) throws Exception {
189: // return (FeatureData)addObject(getProjectId(UserStory.class, feature.getStoryId()), feature);
190: // }
191: //
192: // public void removeFeature(int id) throws Exception {
193: // removeObject(FeatureData.class, id);
194: // }
195: //
196: // public void update(FeatureData object) throws Exception {
197: // updateObject(object);
198: // }
199:
200: //
201: // Tasks
202: //
203:
204: public TaskData getTask(int id) throws Exception {
205: return (TaskData) getObject(TaskData.class, id);
206: }
207:
208: public TaskData[] getCurrentTasksForPerson(int personId)
209: throws QueryException {
210: final TaskQueryHelper taskQueryHelper = (TaskQueryHelper) getSpringBean("taskQueryHelper");
211: taskQueryHelper.setPersonId(personId);
212: return (TaskData[]) toArray(TaskData.class, taskQueryHelper
213: .getCurrentActiveTasksForPerson());
214: }
215:
216: public TaskData[] getPlannedTasksForPerson(int personId)
217: throws QueryException {
218: final TaskQueryHelper taskQueryHelper = (TaskQueryHelper) getSpringBean("taskQueryHelper");
219: taskQueryHelper.setPersonId(personId);
220: taskQueryHelper.setPersonId(personId);
221: return (TaskData[]) toArray(TaskData.class, taskQueryHelper
222: .getCurrentPendingTasksForPerson());
223: }
224:
225: public TaskData addTask(TaskData task) throws Exception {
226: return (TaskData) addObject(getProjectId(UserStory.class, task
227: .getStoryId()), task);
228: }
229:
230: public void removeTask(int id) throws Exception {
231: removeObject(TaskData.class, id);
232: }
233:
234: public void update(TaskData object) throws Exception {
235: updateObject(object);
236: }
237:
238: //
239: // Time Entries
240: //
241:
242: public TimeEntryData[] getTimeEntries(int containerId)
243: throws Exception {
244: return (TimeEntryData[]) getObjects(TaskData.class,
245: containerId, "timeEntries", TimeEntryData.class);
246: }
247:
248: public TimeEntryData getTimeEntry(int id) throws Exception {
249: return (TimeEntryData) getObject(TimeEntryData.class, id);
250: }
251:
252: public TimeEntryData addTimeEntry(TimeEntryData timeEntry)
253: throws Exception {
254: return (TimeEntryData) addObject(getProjectId(Task.class,
255: timeEntry.getTaskId()), timeEntry);
256: }
257:
258: public void removeTimeEntry(int id) throws Exception {
259: removeObject(TimeEntryData.class, id);
260: }
261:
262: public void update(TimeEntryData object) throws Exception {
263: updateObject(object);
264: }
265:
266: //
267: // Notes
268: //
269:
270: public NoteData getNote(int id) throws Exception {
271: return (NoteData) getObject(NoteData.class, id);
272: }
273:
274: public NoteData addNote(NoteData note) throws Exception {
275: return (NoteData) addObject(getProjectId(DomainContext
276: .getNoteTarget(note.getAttachedToId())), note);
277: }
278:
279: public void removeNote(int id) throws Exception {
280: removeObject(NoteData.class, id);
281: }
282:
283: public void update(NoteData note) throws Exception {
284: updateObject(note);
285: }
286:
287: public NoteData[] getNotesForObject(int attachedToId)
288: throws Exception {
289: return (NoteData[]) getObjects(NoteData.class,
290: "attachedTo_Id = " + attachedToId, null, null, null);
291: }
292:
293: //
294: // People
295: //
296:
297: public PersonData getPerson(int id) throws Exception {
298: return (PersonData) getObject(PersonData.class, id);
299: }
300:
301: public PersonData addPerson(PersonData object) throws Exception {
302: return (PersonData) addObject(0, object);
303: }
304:
305: public void removePerson(int id) throws Exception {
306: removeObject(PersonData.class, id);
307: }
308:
309: public void update(PersonData object) throws Exception {
310: updateObject(object);
311: }
312:
313: public PersonData[] getPeople() throws Exception {
314: return (PersonData[]) getObjects(PersonData.class, null, null,
315: null, null);
316: }
317:
318: //
319: // Attributes
320: //
321:
322: public void setAttribute(int objectId, String key, String value)
323: throws Exception {
324: attributes.setAttribute(objectId, key, value);
325: commit(ThreadSession.get());
326: }
327:
328: public String getAttribute(int objectId, String key)
329: throws Exception {
330: return attributes.getAttribute(objectId, key);
331: }
332:
333: public void deleteAttribute(int objectId, String key)
334: throws Exception {
335: attributes.delete(objectId, key);
336: commit(ThreadSession.get());
337: }
338:
339: public Map getAttributes(int objectId) throws Exception {
340: return attributes.getAttributes(objectId, null);
341: }
342:
343: public Map getAttributesWithPrefix(int objectId, String prefix)
344: throws Exception {
345: return attributes.getAttributes(objectId, prefix);
346: }
347:
348: //
349: // Support Functions
350: //
351:
352: private int getProjectId(Class containerClass, int containerId)
353: throws Exception {
354: Object object = ThreadSession.get().load(containerClass,
355: new Integer(containerId));
356: DomainContext context = new DomainContext();
357: context.populate(object);
358: return context.getProjectId();
359: }
360:
361: private int getProjectId(Object object) throws Exception {
362: DomainContext context = new DomainContext();
363: context.populate(object);
364: return context.getProjectId();
365: }
366:
367: private Object[] getObjects(Class dataClass, String where,
368: Object[] values, Type[] types, String orderBy)
369: throws Exception {
370: try {
371: Session session = ThreadSession.get();
372: Class objectClass = getInternalClass(dataClass);
373: String query = "from object in class "
374: + objectClass.getName();
375: if (where != null) {
376: query += " where " + where;
377: }
378: if (orderBy != null) {
379: query += " order by " + orderBy;
380: }
381: List objects = values != null ? session.find(query, values,
382: types) : session.find(query);
383: return toArray(dataClass, objects);
384: } catch (Exception ex) {
385: log.error("error loading objects", ex);
386: throw ex;
387: }
388: }
389:
390: private Object[] getObjects(Class fromDataClass, int id,
391: String propertyName, Class toDataClass) throws Exception {
392: try {
393: Session session = ThreadSession.get();
394: Class objectClass = getInternalClass(fromDataClass);
395: log.debug("getting object: " + id);
396: Object object = session.load(objectClass, new Integer(id));
397: log.debug("loaded object: " + object);
398: Collection objects = (Collection) PropertyUtils
399: .getProperty(object, propertyName);
400: Object[] dataArray = toArray(toDataClass, objects);
401: return dataArray;
402: } catch (Exception ex) {
403: log.error("error loading objects", ex);
404: throw ex;
405: }
406: }
407:
408: private Object getObject(Class dataClass, int id) throws Exception {
409: Session session = ThreadSession.get();
410: try {
411: Class objectClass = getInternalClass(dataClass);
412: log.debug("getting object: " + id);
413: DomainObject object = (DomainObject) session.load(
414: objectClass, new Integer(id));
415: log.debug("loaded object: " + object);
416: DomainData data = (DomainData) dataClass.newInstance();
417: if (hasPermission(getProjectId(object), object, "read")) {
418: populateDomainData(data, object);
419: return data;
420: } else {
421: throw new AuthenticationException(
422: "no permission to read object");
423: }
424: } catch (ObjectNotFoundException ex) {
425: return null;
426: } catch (Exception ex) {
427: log.error("error loading objects", ex);
428: throw ex;
429: }
430: }
431:
432: static Integer NULL = new Integer(-1);
433:
434: private void updateObject(DomainData data) throws Exception {
435: Session session = null;
436: try {
437: Integer id = NULL;
438: session = ThreadSession.get();
439: Class objectClass = getInternalClass(data.getClass());
440: id = getObjectId(data);
441: DomainObject object = (DomainObject) session.load(
442: objectClass, id);
443: if (hasPermission(getProjectId(object), object, "edit")) {
444: // JM: no need to write lock the object
445: // There is a lot more chance to get the client out-of-sync during a get/update than just in that method
446: // See to-do at the top of the file for better implementation
447: if (object != null) {
448: populateDomainObject(object, data);
449: }
450: saveHistory(session, object, HistoricalEvent.UPDATED);
451: commit(session);
452: } else {
453: throw new AuthenticationException(
454: "no permission to update object");
455: }
456: } catch (Exception ex) {
457: rollback(session);
458: throw ex;
459: }
460: }
461:
462: private void saveHistory(Session session, DomainObject object,
463: String eventType) throws AuthenticationException {
464: String description = null;
465: if (eventType.equals(HistoricalEvent.DELETED)) {
466: try {
467: description = BeanUtils.getProperty(object, "name");
468: } catch (Exception e) {
469: description = "unknown name";
470: }
471: }
472: HistorySupport.saveEvent(session, object, eventType,
473: description, SecurityHelper
474: .getRemoteUserId(ThreadServletRequest.get()),
475: new Date());
476: }
477:
478: private Integer getObjectId(Object data)
479: throws NoSuchMethodException, IllegalAccessException,
480: InvocationTargetException {
481: return (Integer) PropertyUtils.getProperty(data, "id");
482: }
483:
484: protected void removeObject(Class dataClass, int id)
485: throws Exception {
486: //DEBT Should use the metarepository
487: Session session = null;
488: try {
489: session = ThreadSession.get();
490: log.debug("removing object: " + id);
491: Class objectClass = getInternalClass(dataClass);
492: DomainObject object = (DomainObject) session.load(
493: objectClass, new Integer(id));
494: if (hasPermission(getProjectId(object),
495: (DomainObject) object, "delete")) {
496: session.delete(object);
497: saveHistory(session, object, HistoricalEvent.DELETED);
498: commit(session);
499: } else {
500: throw new AuthenticationException(
501: "no permission to delete object");
502: }
503: } catch (ObjectNotFoundException ex) {
504: throw ex;
505: } catch (Exception ex) {
506: rollback(session);
507: throw ex;
508: }
509: }
510:
511: protected Object addObject(int projectId, DomainData data)
512: throws Exception {
513: Session session = null;
514: try {
515: session = ThreadSession.get();
516: Class objectClass = getInternalClass(data.getClass());
517: DomainObject object = (DomainObject) objectClass
518: .newInstance();
519: if (hasPermission(projectId, object, "create")) {
520: populateDomainObject(object, data);
521: log.debug("adding object: " + object);
522: Serializable id = session.save(object);
523: saveHistory(session, object, HistoricalEvent.CREATED);
524: commit(session);
525: // return getObject(data.getClass(), ((Integer) id).intValue());
526: populateDomainData(data, object);
527: return data;
528: } else {
529: throw new AuthenticationException(
530: "no permission to create object");
531: }
532: } catch (Exception ex) {
533: rollback(session);
534: throw ex;
535: }
536: }
537:
538: private void commit(Session session) {
539: if (session == null)
540: return;
541: try {
542: session.flush();
543: session.connection().commit();
544: } catch (Exception ex) {
545: log.error("error", ex);
546: throw new RuntimeException(ex);
547: }
548: }
549:
550: private void rollback(Session session) {
551: if (session == null)
552: return;
553: try {
554: session.connection().rollback();
555: } catch (Exception e) {
556: log.error("error", e);
557: }
558: }
559:
560: private void closeSession(Session session) {
561: if (session == null)
562: return;
563: try {
564: session.close();
565: } catch (Exception ex) {
566: log.error("error", ex);
567: }
568: }
569:
570: private Object[] toArray(Class dataClass, Collection objects) {
571: try {
572: ArrayList accessibleObjects = selectAccessibleObjects(objects);
573: Object[] dataObjects = createArray(dataClass,
574: accessibleObjects);
575: Iterator iter = accessibleObjects.iterator();
576: for (int i = 0; iter.hasNext(); i++) {
577: populateDomainData((DomainData) dataObjects[i],
578: (DomainObject) iter.next());
579: }
580: return dataObjects;
581: } catch (Exception ex) {
582: log.error("error in toArray", ex);
583: return null;
584: }
585: }
586:
587: private ArrayList selectAccessibleObjects(Collection objects)
588: throws Exception {
589: ArrayList accessibleObjects = new ArrayList();
590: for (Iterator objectIterator = objects.iterator(); objectIterator
591: .hasNext();) {
592: DomainObject object = (DomainObject) objectIterator.next();
593: if (hasPermission(getProjectId(object), object, "read")) {
594: accessibleObjects.add(object);
595: }
596:
597: }
598: return accessibleObjects;
599: }
600:
601: private boolean hasPermission(int projectId,
602: DomainObject sourceObject, String permission)
603: throws Exception {
604: int remoteUserId = SecurityHelper
605: .getRemoteUserId(ThreadServletRequest.get());
606: log.info("Checking permission for userid " + remoteUserId);
607: return SystemAuthorizer.get().hasPermission(projectId,
608: remoteUserId, sourceObject, permission);
609: }
610:
611: private void populateDomainData(DomainData data,
612: DomainObject sourceObject) throws IllegalAccessException,
613: InvocationTargetException, NoSuchMethodException {
614: BeanUtils.copyProperties(data, sourceObject);
615: Map description = PropertyUtils.describe(data);
616: Iterator keyItr = description.keySet().iterator();
617: while (keyItr.hasNext()) {
618: String key = (String) keyItr.next();
619: if ("class".equals(key)
620: || "relationshipMapping".equals(key))
621: continue;
622: if (isRelationship(sourceObject, key)) {
623: RelationshipMappingRegistry.getInstance()
624: .getRelationshipMapping(sourceObject, key)
625: .populateAdapter(data, sourceObject);
626: }
627: }
628: }
629:
630: private void populateDomainObject(DomainObject targetObject,
631: DomainData data) throws IllegalAccessException,
632: InvocationTargetException, NoSuchMethodException,
633: HibernateException {
634: BeanUtils.copyProperties(targetObject, data);
635: Map description = PropertyUtils.describe(data);
636: Iterator keyItr = description.keySet().iterator();
637: while (keyItr.hasNext()) {
638: String key = (String) keyItr.next();
639: if ("class".equals(key)
640: || "relationshipMapping".equals(key))
641: continue;
642: if (isRelationship(targetObject, key)) {
643: RelationshipMappingRegistry.getInstance()
644: .getRelationshipMapping(targetObject, key)
645: .populateDomainObject(targetObject, data);
646: }
647: }
648: }
649:
650: private boolean isRelationship(DomainObject domainObject, String key) {
651: return RelationshipMappingRegistry.getInstance()
652: .getRelationshipMapping(domainObject, key) != null;
653: }
654:
655: private Object[] createArray(Class dataClass, Collection objects)
656: throws InstantiationException, IllegalAccessException {
657: Object[] dataObjects = (Object[]) Array.newInstance(dataClass,
658: objects.size());
659: for (int i = 0; i < dataObjects.length; i++) {
660: dataObjects[i] = dataClass.newInstance();
661: }
662: return dataObjects;
663: }
664:
665: private Class getInternalClass(Class dataClass) {
666: try {
667: Method method = dataClass.getMethod("getInternalClass",
668: null);
669: return (Class) method.invoke(dataClass, null);
670: } catch (Exception e) {
671: return getDomainClassForDataClass(dataClass);
672: }
673: }
674:
675: private Class getDomainClassForDataClass(Class dataClass) {
676: return (Class) dataToDomainClassMap.get(dataClass);
677: }
678:
679: static Map dataToDomainClassMap = createDataToDomainClassMap();
680:
681: private static Map createDataToDomainClassMap() {
682: HashMap map = new HashMap();
683: map.put(ProjectData.class, Project.class);
684: map.put(IterationData.class, Iteration.class);
685: map.put(UserStoryData.class, UserStory.class);
686: map.put(TaskData.class, Task.class);
687: map.put(TimeEntryData.class, TimeEntry.class);
688: map.put(PersonData.class, Person.class);
689: map.put(NoteData.class, Note.class);
690: return map;
691: }
692:
693: Object getSpringBean(String beanName) {
694: return MainBeanFactory.getBean(beanName, true);
695: }
696: }
|