001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.core.security;
034:
035: import com.flexive.shared.*;
036: import com.flexive.shared.content.FxPK;
037: import com.flexive.shared.exceptions.FxApplicationException;
038: import com.flexive.shared.interfaces.AccountEngine;
039: import com.flexive.shared.security.*;
040: import com.flexive.shared.structure.FxEnvironment;
041: import org.apache.commons.lang.ArrayUtils;
042: import org.apache.commons.logging.Log;
043: import org.apache.commons.logging.LogFactory;
044:
045: import java.io.Serializable;
046: import java.util.ArrayList;
047: import java.util.Enumeration;
048: import java.util.Hashtable;
049: import java.util.List;
050:
051: /**
052: * Implementation of the interface UserTicket.<br>
053: * The UserTicket caches informations about a user.
054: *
055: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
056: */
057: public class UserTicketImpl implements UserTicket, Serializable {
058: private static final long serialVersionUID = -8780256553138578843L;
059: private static transient Log LOG = LogFactory
060: .getLog(UserTicketImpl.class);
061:
062: //private static transient Log LOG = LogFactory.getLog(UserTicketImpl.class);
063: private final String userName;
064: private final String loginName;
065: private final long userId;
066: private final boolean multiLogin;
067: private final long mandator;
068: private final String applicationId;
069: private final boolean webDav;
070: private FxLanguage language;
071:
072: private boolean globalSupervisor;
073: private boolean mandatorSupervisor;
074: private long[] groups;
075: private Role[] roles;
076: private boolean dirty;
077: private ACLAssignment[] assignments;
078: private long creationTime;
079: private FxPK contactData;
080:
081: private static long STRUCTURE_TIMESTAMP = -1;
082: private static ACLAssignment[] guestACLAssignments = null;
083: private static Role[] guestRoles = null;
084: private static long[] guestGroups = null;
085: private static FxPK guestContactData = new FxPK(1); // will be updated when the environment is available
086: private long failedLoginAttempts = 0;
087: private AuthenticationSource authenticationSource = AuthenticationSource.None;
088:
089: /**
090: * Returns a guest ticket, based on the request information data.
091: * <p/>
092: * The guest ticket always belong to the MANDATOR_PUBLIC.
093: *
094: * @return the guest ticket
095: */
096: public static synchronized UserTicket getGuestTicket() {
097: FxContext si = FxContext.get();
098: if (CacheAdmin.isEnvironmentLoaded()
099: && CacheAdmin.getEnvironment().getTimeStamp() != STRUCTURE_TIMESTAMP) {
100: STRUCTURE_TIMESTAMP = CacheAdmin.getEnvironment()
101: .getTimeStamp();
102: reloadGuestTicketAssignments(false);
103: }
104: return new UserTicketImpl(si.getApplicationId(), si.isWebDAV(),
105: "GUEST", "GUEST", Account.USER_GUEST, guestContactData,
106: Mandator.MANDATOR_FLEXIVE, true, guestGroups,
107: guestRoles, guestACLAssignments, FxLanguage.DEFAULT, 0,
108: AuthenticationSource.None);
109: }
110:
111: /**
112: * (Re)load all assignments for the guest user ticket
113: *
114: * @param flagDirty flag the UserTicketStores guest ticket as dirty?
115: */
116: public static synchronized void reloadGuestTicketAssignments(
117: boolean flagDirty) {
118: try {
119: AccountEngine accountInterface = EJBLookup
120: .getAccountEngine();
121: guestACLAssignments = accountInterface
122: .loadAccountAssignments(Account.USER_GUEST);
123: guestRoles = accountInterface.getRoles(Account.USER_GUEST,
124: AccountEngine.RoleLoadMode.ALL);
125: guestGroups = accountInterface
126: .getGroups(Account.USER_GUEST).toLongArray();
127: guestContactData = accountInterface
128: .load(Account.USER_GUEST).getContactData();
129: if (flagDirty)
130: UserTicketStore
131: .flagDirtyHavingUserId(Account.USER_GUEST);
132: } catch (FxApplicationException e) {
133: guestACLAssignments = null;
134: LOG.error(e);
135: }
136: }
137:
138: /**
139: * {@inheritDoc}
140: */
141: public final boolean isInRole(Role role) {
142: return isGlobalSupervisor() || roles != null
143: && FxArrayUtils.containsElement(roles, role);
144: }
145:
146: /**
147: * {@inheritDoc}
148: */
149: public String getApplicationId() {
150: return this .applicationId;
151: }
152:
153: /**
154: * {@inheritDoc}
155: */
156: public boolean isInGroup(long group) {
157: return FxArrayUtils.containsElement(groups, group);
158: }
159:
160: /**
161: * If true the ticket is dirty and needs to be synced with the database.
162: *
163: * @return true the ticket is dirty and needs to be synced with the database
164: */
165: public boolean isDirty() {
166: return dirty;
167: }
168:
169: /**
170: * Sets the dirty flag.
171: *
172: * @param value true the ticket is dirty and needs to be synced with the database
173: */
174: public void setDirty(boolean value) {
175: this .dirty = value;
176: }
177:
178: /**
179: * {@inheritDoc}
180: */
181: public boolean isInGroups(int groups[]) {
182: if (groups == null || groups.length == 0) {
183: return true;
184: }
185: for (int group : groups) {
186: if (!FxArrayUtils.containsElement(this .groups, group)) {
187: return false;
188: }
189: }
190: return true;
191: }
192:
193: /**
194: * {@inheritDoc}
195: */
196: public boolean isInAtLeastOneGroup(long[] groups) {
197: if (groups == null || groups.length == 0) {
198: return false;
199: }
200: for (long group : groups) {
201: if (FxArrayUtils.containsElement(this .groups, group)) {
202: return true;
203: }
204: }
205: return false;
206: }
207:
208: /**
209: * Returns true if the user is assigned to at least one of the given ACLs.
210: * <p/>
211: * <p/>
212: * Returns false if the acls parameter is null or empty
213: *
214: * @param acls the ACLs to check for
215: * @return true if the user is a assigned to at least one of the given ACLs
216: */
217: public boolean hasAtLeastOneACL(long[] acls) {
218: if (assignments == null || assignments.length == 0
219: || acls == null || acls.length == 0) {
220: return false;
221: }
222: for (ACLAssignment aclData : assignments) {
223: for (long acl : acls) {
224: if (aclData.getAclId() == acl) {
225: return true;
226: }
227: }
228: }
229: return false;
230: }
231:
232: /**
233: * {@inheritDoc}
234: */
235: public boolean isGuest() {
236: return (userId == Account.USER_GUEST);
237: }
238:
239: /**
240: * {@inheritDoc}
241: */
242: public String getUserName() {
243: return userName;
244: }
245:
246: /**
247: * {@inheritDoc}
248: */
249: public String getLoginName() {
250: return loginName;
251: }
252:
253: /**
254: * {@inheritDoc}
255: */
256: public long getUserId() {
257: return userId;
258: }
259:
260: /**
261: * {@inheritDoc}
262: */
263: public FxPK getContactData() {
264: return contactData;
265: }
266:
267: /**
268: * {@inheritDoc}
269: */
270: public boolean isMultiLogin() {
271: return multiLogin;
272: }
273:
274: /**
275: * {@inheritDoc}
276: */
277: public boolean isGlobalSupervisor() {
278: return globalSupervisor;
279: }
280:
281: /**
282: * {@inheritDoc}
283: */
284: public boolean isMandatorSupervisor() {
285: return (mandatorSupervisor || globalSupervisor);
286: }
287:
288: /**
289: * {@inheritDoc}
290: */
291: public long getMandatorId() {
292: return this .mandator;
293: }
294:
295: /**
296: * {@inheritDoc}
297: */
298: public long[] getGroups() {
299: return ArrayUtils.clone(this .groups);
300: }
301:
302: /**
303: * Constructor.
304: *
305: * @param applicationId the application id this ticket belongs to
306: * @param acc the account
307: * @param groups the groups
308: * @param roles the roles
309: * @param aad the acl assignemnts
310: * @param language the language
311: * @param isWebDav true if this is a webdav ticket
312: */
313: public UserTicketImpl(String applicationId, boolean isWebDav,
314: Account acc, long[] groups, Role[] roles,
315: ACLAssignment aad[], FxLanguage language) {
316: this .userName = acc.getName();
317: this .loginName = acc.getLoginName();
318: this .userId = acc.getId();
319: this .contactData = acc.getContactData();
320: this .multiLogin = acc.isAllowMultiLogin();
321: this .roles = (Role[]) ArrayUtils.clone(roles);
322: this .groups = ArrayUtils.clone(groups);
323: this .mandator = acc.getMandatorId();
324: this .applicationId = applicationId;
325: this .assignments = FxArrayUtils.clone(aad);
326: this .language = language;
327: this .webDav = isWebDav;
328: populateData();
329: }
330:
331: /**
332: * Private Constructor.
333: *
334: * @param applicationId the application name that the ticket belongs to
335: * @param userName the user name
336: * @param loginName the login name
337: * @param userId the user id
338: * @param contactData contact data pk
339: * @param mandatorId the mandator id that the user belongs to
340: * @param multiLogin true if the account may be logged in more than once at a time
341: * @param groups the groups
342: * @param roles the roles
343: * @param assignments the acl assignemnts
344: * @param language the language
345: * @param isWebDav true if this is a webdav ticket
346: * @param failedLoginAttempts number of failed login attempts
347: * @param authenticationSource source of authentication
348: */
349: private UserTicketImpl(String applicationId, boolean isWebDav,
350: String userName, String loginName, long userId,
351: FxPK contactData, long mandatorId, boolean multiLogin,
352: long[] groups, Role[] roles, ACLAssignment assignments[],
353: FxLanguage language, long failedLoginAttempts,
354: AuthenticationSource authenticationSource) {
355: this .applicationId = applicationId;
356: this .userName = userName;
357: this .loginName = loginName;
358: this .userId = userId;
359: this .contactData = contactData;
360: this .multiLogin = multiLogin;
361: this .groups = groups;
362: this .roles = roles;
363: this .mandator = mandatorId;
364: this .assignments = assignments;
365: this .language = language;
366: this .webDav = isWebDav;
367: this .failedLoginAttempts = failedLoginAttempts;
368: this .authenticationSource = authenticationSource;
369: populateData();
370: }
371:
372: /**
373: * Helper function for the constructor.
374: */
375: private void populateData() {
376:
377: this .dirty = false;
378: this .creationTime = System.currentTimeMillis();
379:
380: // Check ACL assignments
381: if (assignments == null) {
382: assignments = new ACLAssignment[0];
383: }
384:
385: // Check groups
386: if (this .groups == null || this .groups.length == 0) {
387: this .groups = new long[] { UserGroup.GROUP_EVERYONE };
388: } else {
389: // Group everyone has to be present
390: if (!(FxArrayUtils.containsElement(this .groups,
391: UserGroup.GROUP_EVERYONE))) {
392: this .groups = ArrayUtils.add(this .groups,
393: UserGroup.GROUP_EVERYONE);
394: }
395: }
396:
397: // Check roles
398: if (this .roles == null) {
399: this .roles = new Role[0];
400: }
401:
402: // Set mandator/global supervisor flag
403: if (this .userId == Account.USER_GLOBAL_SUPERVISOR
404: || isInRole(Role.GlobalSupervisor)) {
405: globalSupervisor = true;
406: }
407: if (isInRole(Role.MandatorSupervisor)) {
408: mandatorSupervisor = true;
409: }
410: }
411:
412: /**
413: * Returns the time that this ticket was created at.
414: *
415: * @return the time that the ticket was created at
416: */
417: public long getCreationTime() {
418: return this .creationTime;
419: }
420:
421: /**
422: * Returns a string representation of the ticket.
423: *
424: * @return a string representation of the ticket.
425: */
426: @Override
427: public String toString() {
428: return this .getClass() + "@[" + "id=" + this .userId
429: + "; contactData=" + this .contactData + "; name:"
430: + this .userName + "; mandator:" + this .mandator
431: + "; language: " + this .getLanguage().getIso2digit()
432: + "; groups:"
433: + FxArrayUtils.toSeparatedList(this .groups, ',')
434: + "; roles:"
435: + FxArrayUtils.toSeparatedList(this .roles, ',')
436: + "; globalSupervisor:" + this .globalSupervisor
437: + "; multiLogin:" + this .multiLogin + "]";
438:
439: }
440:
441: public UserTicketImpl cloneAsGlobalSupervisor() {
442: UserTicketImpl clone = new UserTicketImpl(this .applicationId,
443: this .webDav, this .userName, this .loginName,
444: this .userId, this .contactData, this .mandator,
445: this .multiLogin, this .groups.clone(), this .roles
446: .clone(),
447: ACLAssignment.clone(this .assignments), this .language,
448: this .failedLoginAttempts, this .authenticationSource);
449: clone.globalSupervisor = true;
450: return clone;
451: }
452:
453: /**
454: * {@inheritDoc}
455: */
456: public ACLAssignment[] getACLAssignments() {
457: return FxArrayUtils.clone(this .assignments);
458: }
459:
460: /**
461: * {@inheritDoc}
462: */
463: public boolean isAssignedToACL(long aclId) {
464: for (ACLAssignment item : this .assignments) {
465: if (item.getAclId() == aclId
466: && !item.isOwnerGroupAssignment())
467: return true;
468: }
469: return false;
470: }
471:
472: /**
473: * {@inheritDoc}
474: */
475: public boolean mayReadACL(long aclId, long ownerId) {
476: if (this .isGlobalSupervisor())
477: return true;
478: for (ACLAssignment item : this .assignments) {
479: if (item.isOwnerGroupAssignment() && ownerId != userId)
480: continue;
481: if (item.getMayRead() && item.getAclId() == aclId)
482: return true;
483: }
484: return false;
485: }
486:
487: /**
488: * {@inheritDoc}
489: */
490: public boolean mayEditACL(long aclId, long ownerId) {
491: if (this .isGlobalSupervisor())
492: return true;
493: for (ACLAssignment item : this .assignments) {
494: if (item.isOwnerGroupAssignment() && ownerId != userId)
495: continue;
496: if (item.getMayEdit() && item.getAclId() == aclId)
497: return true;
498: }
499: return false;
500: }
501:
502: /**
503: * {@inheritDoc}
504: */
505: public boolean mayExportACL(long aclId, long ownerId) {
506: if (this .isGlobalSupervisor())
507: return true;
508: for (ACLAssignment item : this .assignments) {
509: if (item.isOwnerGroupAssignment() && ownerId != userId)
510: continue;
511: if (item.getMayExport() && item.getAclId() == aclId)
512: return true;
513: }
514: return false;
515: }
516:
517: /**
518: * {@inheritDoc}
519: */
520: public boolean mayRelateACL(long aclId, long ownerId) {
521: if (this .isGlobalSupervisor())
522: return true;
523: for (ACLAssignment item : this .assignments) {
524: if (item.isOwnerGroupAssignment() && ownerId != userId)
525: continue;
526: if (item.getMayRelate() && item.getAclId() == aclId)
527: return true;
528: }
529: return false;
530: }
531:
532: /**
533: * {@inheritDoc}
534: */
535: public boolean mayCreateACL(long aclId, long ownerId) {
536: if (this .isGlobalSupervisor())
537: return true;
538: for (ACLAssignment item : this .assignments) {
539: if (item.isOwnerGroupAssignment())
540: continue; //group owner may never create!
541: if (item.getMayCreate() && item.getAclId() == aclId)
542: return true;
543: }
544: return false;
545: }
546:
547: /**
548: * {@inheritDoc}
549: */
550: public boolean mayDeleteACL(long aclId, long ownerId) {
551: if (this .isGlobalSupervisor())
552: return true;
553: for (ACLAssignment item : this .assignments) {
554: if (item.isOwnerGroupAssignment() && ownerId != userId)
555: continue;
556: if (item.getMayDelete() && item.getAclId() == aclId)
557: return true;
558: }
559: return false;
560: }
561:
562: /**
563: * {@inheritDoc}
564: */
565: public ACLAssignment[] getACLAssignments(ACL.Category category,
566: long ownerId, ACL.Permission... perms) {
567: Boolean mayCreate = null;
568: Boolean mayRead = null;
569: Boolean mayEdit = null;
570: Boolean mayDelete = null;
571: Boolean mayRelate = null;
572: Boolean mayExport = null;
573: for (ACL.Permission perm : perms) {
574: switch (perm) {
575: case CREATE:
576: mayCreate = true;
577: break;
578: case NOT_CREATE:
579: mayCreate = false;
580: break;
581: case READ:
582: mayRead = true;
583: break;
584: case NOT_READ:
585: mayRead = false;
586: break;
587: case EDIT:
588: mayEdit = true;
589: break;
590: case NOT_EDIT:
591: mayEdit = false;
592: break;
593: case DELETE:
594: mayDelete = true;
595: break;
596: case NOT_DELETE:
597: mayDelete = false;
598: break;
599: case RELATE:
600: mayRelate = true;
601: break;
602: case NOT_RELATE:
603: mayRelate = false;
604: break;
605: case EXPORT:
606: mayExport = true;
607: break;
608: case NOT_EXPORT:
609: mayExport = false;
610: break;
611: }
612: }
613: List<ACLAssignment> result = new ArrayList<ACLAssignment>(
614: this .assignments.length);
615: for (ACLAssignment acl : this .assignments) {
616: if (acl.isOwnerGroupAssignment() && ownerId != userId)
617: continue;
618: if (mayRead != null && mayRead != acl.getMayRead())
619: continue;
620: if (mayEdit != null && mayEdit != acl.getMayEdit())
621: continue;
622: if (mayDelete != null && mayDelete != acl.getMayDelete())
623: continue;
624: if (mayRelate != null && mayRelate != acl.getMayRelate())
625: continue;
626: if (mayExport != null && mayExport != acl.getMayExport())
627: continue;
628: if (mayCreate != null && mayCreate != acl.getMayCreate())
629: continue;
630: if (category != null && category != acl.getACLCategory())
631: continue;
632: result.add(acl);
633: }
634: return result.toArray(new ACLAssignment[result.size()]);
635: }
636:
637: /**
638: * {@inheritDoc}
639: */
640: public String getACLsCSV(long ownerId, ACL.Category category,
641: ACL.Permission... perms) {
642: String result = "";
643: Long ACLs[] = getACLsId(ownerId, category, perms);
644: for (long acl : ACLs) {
645: result += ((result.length() > 0) ? "," : "") + acl;
646: }
647: return result;
648: }
649:
650: /**
651: * {@inheritDoc}
652: */
653: public Long[] getACLsId(long ownerId, ACL.Category category,
654: ACL.Permission... perms) {
655: Boolean mayCreate = null;
656: Boolean mayRead = null;
657: Boolean mayEdit = null;
658: Boolean mayDelete = null;
659: Boolean mayRelate = null;
660: Boolean mayExport = null;
661: for (ACL.Permission perm : perms) {
662: switch (perm) {
663: case CREATE:
664: mayCreate = true;
665: break;
666: case NOT_CREATE:
667: mayCreate = false;
668: break;
669: case READ:
670: mayRead = true;
671: break;
672: case NOT_READ:
673: mayRead = false;
674: break;
675: case EDIT:
676: mayEdit = true;
677: break;
678: case NOT_EDIT:
679: mayEdit = false;
680: break;
681: case DELETE:
682: mayDelete = true;
683: break;
684: case NOT_DELETE:
685: mayDelete = false;
686: break;
687: case RELATE:
688: mayRelate = true;
689: break;
690: case NOT_RELATE:
691: mayRelate = false;
692: break;
693: case EXPORT:
694: mayExport = true;
695: break;
696: case NOT_EXPORT:
697: mayExport = false;
698: break;
699: }
700: }
701: Hashtable<Long, boolean[]> hlp = new Hashtable<Long, boolean[]>(
702: this .assignments.length);
703:
704: // Condense the ACL right informations
705: // If a ACL is assigned via groupX and groupY the rights are taken from both assignments.
706: for (ACLAssignment acl : this .assignments) {
707: if (acl.isOwnerGroupAssignment() && ownerId != userId)
708: continue;
709: if (category != null && acl.getACLCategory() != category)
710: continue;
711: Long key = acl.getAclId();
712: boolean[] rights = hlp.get(key);
713: if (rights == null) {
714: rights = new boolean[] { false, false, false, false,
715: false, false, false };
716: }
717: if (acl.getMayRead())
718: rights[ACL.Permission.READ.ordinal()] = true;
719: if (acl.getMayEdit())
720: rights[ACL.Permission.EDIT.ordinal()] = true;
721: if (acl.getMayDelete())
722: rights[ACL.Permission.DELETE.ordinal()] = true;
723: if (acl.getMayRelate())
724: rights[ACL.Permission.RELATE.ordinal()] = true;
725: if (acl.getMayExport())
726: rights[ACL.Permission.EXPORT.ordinal()] = true;
727: if (acl.getMayCreate() && !acl.isOwnerGroupAssignment())
728: rights[ACL.Permission.CREATE.ordinal()] = true;
729: hlp.put(key, rights);
730: }
731:
732: // Return matching ACLs
733: Enumeration keys = hlp.keys();
734: List<Long> result = new ArrayList<Long>(hlp.size());
735: while (keys.hasMoreElements()) {
736: Long aclId = (Long) keys.nextElement();
737: boolean[] rights = hlp.get(aclId);
738: if (mayRead != null
739: && mayRead != rights[ACL.Permission.READ.ordinal()])
740: continue;
741: if (mayEdit != null
742: && mayEdit != rights[ACL.Permission.EDIT.ordinal()])
743: continue;
744: if (mayDelete != null
745: && mayDelete != rights[ACL.Permission.DELETE
746: .ordinal()])
747: continue;
748: if (mayRelate != null
749: && mayRelate != rights[ACL.Permission.RELATE
750: .ordinal()])
751: continue;
752: if (mayExport != null
753: && mayExport != rights[ACL.Permission.EXPORT
754: .ordinal()])
755: continue;
756: if (mayCreate != null
757: && mayCreate != rights[ACL.Permission.CREATE
758: .ordinal()])
759: continue;
760: result.add(aclId);
761: }
762: return result.toArray(new Long[result.size()]);
763: }
764:
765: /**
766: * {@inheritDoc}
767: */
768: public ACL[] getACLs(long owner, ACL.Category category,
769: ACL.Permission... perms) {
770: Long[] acls = getACLsId(owner, category, perms);
771: List<ACL> res = new ArrayList<ACL>(acls.length);
772: FxEnvironment struct;
773: struct = CacheAdmin.getEnvironment();
774: for (Long acl : acls) {
775: res.add(struct.getACL(acl));
776: }
777: return res.toArray(new ACL[res.size()]);
778: }
779:
780: /**
781: * {@inheritDoc}
782: */
783: public FxLanguage getLanguage() {
784: return language;
785: }
786:
787: /**
788: * {@inheritDoc}
789: */
790: public boolean isWebDav() {
791: return this .webDav;
792: }
793:
794: public void setFailedLoginAttempts(long failedLoginAttempts) {
795: this .failedLoginAttempts = failedLoginAttempts;
796: }
797:
798: public void setAuthenticationSource(
799: AuthenticationSource authenticationSource) {
800: this .authenticationSource = authenticationSource;
801: }
802:
803: /**
804: * {@inheritDoc}
805: */
806: public long getFailedLoginAttempts() {
807: return failedLoginAttempts;
808: }
809:
810: /**
811: * {@inheritDoc}
812: */
813: public AuthenticationSource getAuthenticationSource() {
814: return authenticationSource;
815: }
816:
817: /**
818: * {@inheritDoc}
819: */
820: public void overrideLanguage(FxLanguage language) {
821: if (language != null)
822: this.language = language;
823: }
824: }
|