001: /*--------------------------------------------------------------------------*
002: | Copyright (C) 2006 Christopher Kohlhaas |
003: | |
004: | This program is free software; you can redistribute it and/or modify |
005: | it under the terms of the GNU General Public License as published by the |
006: | Free Software Foundation. A copy of the license has been included with |
007: | these distribution in the COPYING file, if not go to www.fsf.org |
008: | |
009: | As a special exception, you are granted the permissions to link this |
010: | program with every library, which license fulfills the Open Source |
011: | Definition as published by the Open Source Initiative (OSI). |
012: *--------------------------------------------------------------------------*/
013: package org.rapla.server.internal;
014:
015: import java.io.BufferedWriter;
016: import java.io.IOException;
017: import java.io.OutputStream;
018: import java.io.OutputStreamWriter;
019: import java.io.StringReader;
020: import java.io.StringWriter;
021: import java.text.ParseException;
022: import java.util.ArrayList;
023: import java.util.Collections;
024: import java.util.Date;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028:
029: import org.apache.avalon.framework.logger.AbstractLogEnabled;
030: import org.apache.avalon.framework.logger.Logger;
031: import org.rapla.components.util.SerializableDateTimeFormat;
032: import org.rapla.components.xmlbundle.I18nBundle;
033: import org.rapla.entities.DependencyException;
034: import org.rapla.entities.RaplaType;
035: import org.rapla.entities.User;
036: import org.rapla.entities.storage.EntityResolver;
037: import org.rapla.entities.storage.Mementable;
038: import org.rapla.entities.storage.RefEntity;
039: import org.rapla.entities.storage.internal.SimpleEntity;
040: import org.rapla.entities.storage.internal.SimpleIdentifier;
041: import org.rapla.framework.RaplaContext;
042: import org.rapla.framework.RaplaDefaultContext;
043: import org.rapla.framework.RaplaException;
044: import org.rapla.server.RemoteService;
045: import org.rapla.server.RemoteSession;
046: import org.rapla.server.RemoteStorage;
047: import org.rapla.server.ShutdownService;
048: import org.rapla.storage.AuthenticationStore;
049: import org.rapla.storage.CachableStorageOperator;
050: import org.rapla.storage.IOContext;
051: import org.rapla.storage.IdTable;
052: import org.rapla.storage.LocalCache;
053: import org.rapla.storage.RaplaSecurityException;
054: import org.rapla.storage.UpdateEvent;
055: import org.rapla.storage.impl.EntityStore;
056: import org.rapla.storage.xml.RaplaInput;
057: import org.rapla.storage.xml.RaplaMainReader;
058: import org.rapla.storage.xml.RaplaMainWriter;
059:
060: /** Provides an adapter for each client-session to their shared storage operator
061: * Handles security and synchronizing aspects.
062: * @author ckohlhaas
063: *
064: */
065: public class RemoteStorageImpl extends AbstractLogEnabled implements
066: RemoteStorage, RemoteService {
067: CachableStorageOperator operator;
068: RemoteSession session;
069:
070: protected SecurityManager security;
071: boolean authenticationStore;
072: ServerServiceImpl server;
073:
074: RemoteStorageImpl(ServerServiceImpl server, RemoteSession session)
075: throws RaplaException {
076: this .server = server;
077: this .session = session;
078: operator = (CachableStorageOperator) getContext().lookup(
079: CachableStorageOperator.ROLE);
080: security = (SecurityManager) getContext().lookup(
081: SecurityManager.class.getName());
082: authenticationStore = getContext()
083: .has(AuthenticationStore.ROLE);
084:
085: enableLogging(session.getLogger());
086: }
087:
088: private void checkAuthentified() throws RaplaSecurityException {
089: if (!session.isAuthentified())
090: throw new RaplaSecurityException(
091: "User was not authentified ");
092: }
093:
094: private User getSessionUser() throws RaplaException {
095: return session.getUser();
096: }
097:
098: public List getResources() throws RaplaException {
099: checkAuthentified();
100: User user = null;
101: this .getLogger().debug(
102: "A RemoteServer wants to get all resource-objects.");
103: synchronized (operator.getLock()) {
104: return makeTransactionSafe(operator
105: .getVisibleEntities(user));
106: }
107: }
108:
109: public void remoteMethodCall(String methodName, Map args,
110: OutputStream out) throws RaplaException, IOException,
111: ParseException {
112: if (!session.isAuthentified()) {
113: throw new SessionExpiredException(
114: "Session on the server not valid anymore. Maybe the server has been restarted");
115: }
116:
117: synchronized (operator.getLock()) {
118:
119: BufferedWriter outWriter = new BufferedWriter(
120: new OutputStreamWriter(out, "utf-8"));
121:
122: RaplaDefaultContext ioContext = new IOContext()
123: .createOutputContext(getContext(), operator
124: .getCache(), true, true);
125: RaplaMainWriter writer = new RaplaMainWriter(ioContext);
126: writer.setWriter(outWriter);
127: if (RemoteStorage.GET_RESOURCES.is(methodName)) {
128: List resources = getResources();
129: writer.printList(resources, Collections.EMPTY_LIST,
130: getRepositoryVersion());
131: } else if (RemoteStorage.GET_RESERVATIONS.is(methodName)) {
132: String startS = RemoteStorage.GET_RESERVATIONS.value(
133: args, 0);
134: String endS = RemoteStorage.GET_RESERVATIONS.value(
135: args, 1);
136: SerializableDateTimeFormat format = new SerializableDateTimeFormat();
137: Date start = null;
138: if (startS != null) {
139: start = format.parseDate(startS, false);
140: }
141: Date end = null;
142: if (endS != null) {
143: end = format.parseDate(endS, true);
144: }
145: List resources = getReservations(start, end);
146: writer.printList(resources, Collections.EMPTY_LIST,
147: getRepositoryVersion());
148: } else if (RemoteStorage.DISPATCH.is(methodName)) {
149: String xml = RemoteStorage.DISPATCH.value(args, 0);
150:
151: LocalCache cache = operator.getCache();
152: UpdateEvent event = createUpdateEvent(getContext(),
153: xml, cache);
154:
155: dispatch(event);
156: } else if (RemoteStorage.CREATE_IDENTIFIER.is(methodName)) {
157: String typeName = RemoteStorage.CREATE_IDENTIFIER
158: .value(args, 0);
159: RaplaType raplaType = RaplaType.find(typeName);
160: SimpleIdentifier id = (SimpleIdentifier) operator
161: .createIdentifier(raplaType);
162: String idString = raplaType.getLocalName() + "_"
163: + id.getKey();
164: outWriter.write(idString);
165: } else if (RemoteStorage.GET_SERVER_TIME.is(methodName)) {
166: String timeString = String.valueOf(getServerTime());
167: outWriter.write(timeString);
168: } else if (RemoteStorage.GET_ENTITY_RECURSIVE
169: .is(methodName)) {
170: String idS = RemoteStorage.GET_ENTITY_RECURSIVE.value(
171: args, 0);
172: int index_ = idS.lastIndexOf('_');
173: String typeName = idS.substring(0, index_);
174: String keyString = idS.substring(index_ + 1);
175: RaplaType raplaType = RaplaType.find(typeName);
176: int key = Integer.parseInt(keyString);
177: // RaplaType raplaType = (RaplaType);
178:
179: SimpleIdentifier id = new SimpleIdentifier(raplaType,
180: key);
181: List resources = getEntityRecursive(id);
182: writer.printList(resources, Collections.EMPTY_LIST,
183: getRepositoryVersion());
184: } else if (RemoteStorage.CAN_CHANGE_PASSWORD.is(methodName)) {
185: String resultString = String
186: .valueOf(canChangePassword());
187: outWriter.write(resultString);
188: } else if (RemoteStorage.CHANGE_PASSWORD.is(methodName)) {
189: String username = RemoteStorage.CHANGE_PASSWORD.value(
190: args, 0);
191: String oldPassword = RemoteStorage.CHANGE_PASSWORD
192: .value(args, 1);
193: String newPassword = RemoteStorage.CHANGE_PASSWORD
194: .value(args, 2);
195: changePassword(username, oldPassword.toCharArray(),
196: newPassword.toCharArray());
197: } else if (RemoteStorage.REFRESH.equals(methodName)) {
198: String time = RemoteStorage.REFRESH.value(args, 0);
199: String xml = server.createUpdateXML(Long.valueOf(time)
200: .longValue());
201: outWriter.write(xml);
202: } else if (RemoteStorage.RESTART_SERVER.equals(methodName)) {
203: restartServer();
204: } else {
205: throw new UnsupportedOperationException("Operation "
206: + methodName + " not available on server.");
207: }
208: outWriter.flush();
209: }
210: }
211:
212: private long getRepositoryVersion() {
213: return server.repositoryVersion;
214: }
215:
216: public I18nBundle getI18n() throws RaplaException {
217: return (I18nBundle) getContext().lookup(
218: I18nBundle.ROLE + "/org.rapla.RaplaResources");
219: }
220:
221: public List getEntityRecursive(Object id) throws RaplaException {
222: checkAuthentified();
223: //synchronized (operator.getLock())
224: {
225: RefEntity entity = operator.resolveId(id);
226: ArrayList completeList = new ArrayList();
227: completeList.add(entity);
228: Iterator it = entity.getSubEntities();
229: while (it.hasNext()) {
230: completeList.add(it.next());
231: }
232: List list = makeTransactionSafe(completeList);
233: getLogger().debug("Get entity " + entity);
234: return list;
235: }
236: }
237:
238: public List getReservations(Date start, Date end)
239: throws RaplaException {
240: checkAuthentified();
241: User user = null;
242: this .getLogger().debug(
243: "A RemoteServer wants to reservations from ." + start
244: + " to " + end);
245: synchronized (operator.getLock()) {
246: ArrayList completeList = new ArrayList();
247: List reservations = operator.getReservations(user, start,
248: end);
249: completeList.addAll(reservations);
250: Iterator it = reservations.iterator();
251: while (it.hasNext()) {
252: Iterator it2 = ((RefEntity) it.next()).getSubEntities();
253: while (it2.hasNext()) {
254: completeList.add(it2.next());
255: }
256: }
257: List list = makeTransactionSafe(completeList);
258: getLogger().debug(
259: "Get reservations " + start + " " + end + ": "
260: + reservations.size() + "," + list.size());
261: return list;
262: }
263: }
264:
265: public RaplaContext getContext() throws RaplaException {
266: return session.getContext();
267: }
268:
269: public void restartServer() throws RaplaException {
270: checkAuthentified();
271: if (!getSessionUser().isAdmin())
272: throw new RaplaSecurityException(
273: "Only admins can restart the server");
274:
275: ((ShutdownService) getContext().lookup(ShutdownService.ROLE))
276: .shutdown(null, true);
277: }
278:
279: public long getServerTime() throws RaplaException {
280: return System.currentTimeMillis();
281: }
282:
283: public void dispatch(UpdateEvent evt) throws RaplaException {
284: checkAuthentified();
285: try {
286: User user;
287: if (evt.getUserId() != null) {
288: user = (User) operator.resolveId(evt.getUserId());
289: } else {
290: user = session.getUser();
291: }
292: synchronized (operator.getLock()) {
293: EntityResolver resolver = operator
294: .createEntityResolver(evt.getStoreObjects(),
295: operator.getCache());
296: Iterator it = evt.getStoreObjects().iterator();
297: while (it.hasNext()) {
298: SimpleEntity entity = (SimpleEntity) it.next();
299: if (getLogger().isDebugEnabled())
300: getLogger().debug("Contextualizing " + entity);
301: entity.resolveEntities(resolver);
302: }
303:
304: it = evt.getRemoveObjects().iterator();
305: while (it.hasNext()) {
306: SimpleEntity entity = (SimpleEntity) it.next();
307: entity.resolveEntities(resolver);
308: }
309:
310: it = evt.getStoreObjects().iterator();
311: while (it.hasNext()) {
312: SimpleEntity entity = (SimpleEntity) it.next();
313: security.checkWritePermissions(user, entity);
314: }
315:
316: it = evt.getRemoveObjects().iterator();
317: while (it.hasNext()) {
318: SimpleEntity entity = (SimpleEntity) it.next();
319: security.checkWritePermissions(user, entity);
320: }
321:
322: if (this .getLogger().isDebugEnabled())
323: this .getLogger().debug(
324: "Dispatching changes to "
325: + operator.getClass());
326:
327: operator.dispatch(evt);
328: if (this .getLogger().isDebugEnabled())
329: this .getLogger().debug(
330: "Changes dispatched returning result.");
331: }
332: } catch (DependencyException ex) {
333: throw ex;
334: } catch (RaplaException ex) {
335: this .getLogger().error(ex.getMessage(), ex);
336: throw ex;
337: } catch (Exception ex) {
338: this .getLogger().error(ex.getMessage(), ex);
339: throw new RaplaException(ex);
340: }
341: }
342:
343: public Object createIdentifier(RaplaType raplaType)
344: throws RaplaException {
345: checkAuthentified();
346: synchronized (operator.getLock()) {
347: //User user =
348: getSessionUser(); //check if authenified
349: return operator.createIdentifier(raplaType);
350: }
351: }
352:
353: public void authenticate(String username, String password)
354: throws RaplaException {
355: synchronized (operator.getLock()) {
356: getSessionUser(); //check if authenified
357: server.authenticate(username, password);
358: }
359: }
360:
361: public boolean canChangePassword() throws RaplaException {
362: checkAuthentified();
363: synchronized (operator.getLock()) {
364: return !authenticationStore && operator.canChangePassword();
365: }
366: }
367:
368: public void changePassword(String username, char[] oldPassword,
369: char[] newPassword) throws RaplaException {
370: checkAuthentified();
371: if (authenticationStore) {
372: throw new RaplaException(
373: "Rapla can't change your password. "
374: + "Authentication is done via plugin.");
375: }
376: synchronized (operator.getLock()) {
377: User sessionUser = getSessionUser();
378: if (!sessionUser.isAdmin()) {
379: operator
380: .authenticate(username, new String(oldPassword));
381: }
382: User user = operator.getUser(username);
383: operator.changePassword(user, oldPassword, newPassword);
384: }
385: }
386:
387: static public UpdateEvent createUpdateEvent(RaplaContext context,
388: String xml, LocalCache cache) throws RaplaException {
389: EntityStore store = new EntityStore(cache, cache
390: .getSuperCategory());
391: RaplaContext inputContext = new IOContext().createInputContext(
392: context, store, new IdTable());
393: Logger logger = (Logger) context.lookup(Logger.class.getName());
394: RaplaInput xmlAdapter = new RaplaInput(logger
395: .getChildLogger("reading"));
396: RaplaMainReader contentHandler = new RaplaMainReader(
397: inputContext);
398: try {
399: xmlAdapter.read(new StringReader(xml), contentHandler,
400: false);
401: } catch (IOException e) {
402: throw new RaplaException(e);
403: }
404: UpdateEvent event = new UpdateEvent();
405: event.setRepositoryVersion(store.getRepositoryVersion());
406: for (Iterator it = store.getList().iterator(); it.hasNext();) {
407: RefEntity object = (RefEntity) it.next();
408: event.putStore(object);
409: }
410: for (Iterator it = store.getRemoveIds().iterator(); it
411: .hasNext();) {
412: Object id = (Object) it.next();
413: RefEntity entity = (RefEntity) cache.get(id);
414: if (entity != null) {
415: event.putRemove(entity);
416: }
417: }
418: return event;
419: }
420:
421: static public String createUpdateEvent(RaplaContext context,
422: LocalCache cache, UpdateEvent evt) throws RaplaException,
423: IOException {
424: RaplaDefaultContext ioContext = new IOContext()
425: .createOutputContext(context, cache, true, true);
426: StringWriter stringWriter = new StringWriter();
427: RaplaMainWriter writer = new RaplaMainWriter(ioContext);
428: BufferedWriter buf = new BufferedWriter(stringWriter);
429: writer.setWriter(buf);
430: writer.printList(evt.getStoreObjects(), evt.getRemoveObjects(),
431: evt.getRepositoryVersion());
432: buf.flush();
433: String xml = stringWriter.toString();
434: return xml;
435: }
436:
437: private static List makeTransactionSafe(List objectList) {
438: List saveList = new ArrayList();
439: Iterator it = objectList.iterator();
440: while (it.hasNext()) {
441: saveList.add(((Mementable) it.next()).clone());
442: }
443: return saveList;
444: }
445:
446: }
|