package org.mobicents.slee.container.profile; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import javax.management.ObjectName; import javax.slee.CreateException; import javax.slee.InvalidArgumentException; import javax.slee.SLEEException; import javax.slee.TransactionRequiredLocalException; import javax.slee.management.ProfileTableNotification; import javax.slee.management.ProfileTableUsageMBean; import javax.slee.profile.AttributeNotIndexedException; import javax.slee.profile.AttributeTypeMismatchException; import javax.slee.profile.ProfileAlreadyExistsException; import javax.slee.profile.ProfileID; import javax.slee.profile.ProfileLocalObject; import javax.slee.profile.ProfileTable; import javax.slee.profile.ProfileTableActivity; import javax.slee.profile.ProfileVerificationException; import javax.slee.profile.ReadOnlyProfileException; import javax.slee.profile.UnrecognizedAttributeException; import javax.slee.profile.UnrecognizedProfileTableNameException; import javax.slee.profile.UnrecognizedQueryNameException; import javax.transaction.SystemException; import org.apache.log4j.Logger; import org.mobicents.slee.container.SleeContainer; import org.mobicents.slee.container.component.ProfileSpecificationComponent; import org.mobicents.slee.container.component.profile.ProfileAttribute; import org.mobicents.slee.container.component.profile.ProfileEntity; import org.mobicents.slee.container.management.jmx.ProfileTableUsageMBeanImpl; import org.mobicents.slee.container.management.jmx.TraceMBeanImpl; import org.mobicents.slee.runtime.activity.ActivityContext; import org.mobicents.slee.runtime.activity.ActivityContextHandle; import org.mobicents.slee.runtime.activity.ActivityContextHandlerFactory; import org.mobicents.slee.runtime.facilities.MNotificationSource; import org.mobicents.slee.runtime.facilities.profile.ProfileTableActivityHandle; import org.mobicents.slee.runtime.facilities.profile.ProfileTableActivityImpl; import org.mobicents.slee.runtime.transaction.TransactionalAction; import org.mobicents.slee.util.concurrent.ConcurrentHashSet; /** * * Start time:11:20:19 2009-03-14<br> * Project: mobicents-jainslee-server-core<br> * * * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> * @author martins */ public class ProfileTableImpl implements ProfileTable, Serializable { /** * */ private static final long serialVersionUID = 1L; public static final int _UNICODE_RANGE_START = 0x0020; public static final int _UNICODE_RANGE_END = 0x007e; public static final int _UNICODE_SLASH = 0x002f; private static final Logger logger = Logger.getLogger(ProfileTableImpl.class); /** * */ private final ProfileSpecificationComponent component; /** * */ private final String profileTableName; /** * */ private final SleeContainer sleeContainer; /** * */ private MNotificationSource profileTableNotification = null; /** * */ private ProfileTableUsageMBeanImpl profileTableUsageMBean = null; /** * indicates if this table fires events */ private final boolean fireEvents; /** * */ private final ProfileTableTransactionView transactionView; /** * the entity with the default profile attribute values */ private ProfileEntity defaultProfileEntity; /** * * @param profileTableName * @param component * @param sleeContainer */ public ProfileTableImpl(final String profileTableName, ProfileSpecificationComponent component, final SleeContainer sleeContainer) { ProfileTableImpl.validateProfileTableName(profileTableName); if (sleeContainer == null || component == null) { throw new NullPointerException(); } this.component = component; this.sleeContainer = sleeContainer; this.profileTableName = profileTableName; this.profileTableNotification = new MNotificationSource( new ProfileTableNotification(this.profileTableName)); this.fireEvents = component.getDescriptor().getEventsEnabled(); this.transactionView = new ProfileTableTransactionView(this); // register tracer try { final TraceMBeanImpl traceMBeanImpl = sleeContainer.getTraceMBean(); traceMBeanImpl.registerNotificationSource(new ProfileTableNotification(profileTableName)); TransactionalAction action2 = new TransactionalAction() { public void execute() { // remove notification sources for profile table traceMBeanImpl.deregisterNotificationSource(new ProfileTableNotification(profileTableName)); } }; sleeContainer.getTransactionManager().addAfterRollbackAction(action2); }catch (SystemException e) { throw new SLEEException("Failure to register Tracer", e); } } /** * * @return */ public SleeContainer getSleeContainer() { return sleeContainer; } /** * * @return */ public boolean doesFireEvents() { return fireEvents; } /** * * @return */ public String getProfileTableName() { return this.profileTableName; } /** * * @return */ public ProfileSpecificationComponent getProfileSpecificationComponent() { return component; } /** * * @return */ public MNotificationSource getProfileTableNotification() { return this.profileTableNotification; } /** * * @return */ public ProfileTableUsageMBeanImpl getProfileTableUsageMBean() { return profileTableUsageMBean; } /** * * @return */ public Collection<ProfileID> getProfiles() { List<ProfileID> result = new ArrayList<ProfileID>(); for (ProfileEntity profileEntity : component.getProfileEntityFramework().findAll(this.getProfileTableName())) { result.add(new ProfileID(profileTableName,profileEntity.getProfileName())); } return Collections.unmodifiableCollection(result); } /** * * @throws ReadOnlyProfileException */ private void checkProfileSpecIsNotReadOnly() throws ReadOnlyProfileException { if (component.getDescriptor().getReadOnly()) { throw new ReadOnlyProfileException(component.toString()); } } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#create(java.lang.String) */ public ProfileLocalObject create(String profileName) throws NullPointerException, IllegalArgumentException, TransactionRequiredLocalException, ReadOnlyProfileException, ProfileAlreadyExistsException, CreateException, SLEEException { sleeContainer.getTransactionManager().mandateTransaction(); checkProfileSpecIsNotReadOnly(); ProfileObject profileObject = createProfile(profileName); profileObject.profilePersist(); return profileObject.getProfileLocalObject(); } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#find(java.lang.String) */ public ProfileLocalObject find(String profileName) throws NullPointerException, TransactionRequiredLocalException, SLEEException { if (profileName == null) { throw new NullPointerException(); } ProfileObject profileObject = getProfile(profileName); return profileObject == null ? null : profileObject.getProfileLocalObject(); } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#findAll() */ public Collection<ProfileLocalObject> findAll() throws TransactionRequiredLocalException, SLEEException { sleeContainer.getTransactionManager().mandateTransaction(); Collection<ProfileLocalObject> result = new ArrayList<ProfileLocalObject>(); for (ProfileEntity profileEntity : component.getProfileEntityFramework().findAll(this.getProfileTableName())) { result.add(transactionView.getProfile(profileEntity).getProfileLocalObject()); } return Collections.unmodifiableCollection(result); } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#remove(java.lang.String) */ public boolean remove(String profileName) throws NullPointerException, ReadOnlyProfileException, TransactionRequiredLocalException, SLEEException { if (profileName == null) { throw new NullPointerException("Profile name must not be null"); } checkProfileSpecIsNotReadOnly(); return this.removeProfile(profileName,true); } /** * * @param profileName * @param invokeConcreteSbb * @return * @throws TransactionRequiredLocalException * @throws SLEEException */ public boolean removeProfile(String profileName, boolean invokeConcreteSbb) throws TransactionRequiredLocalException, SLEEException { if (logger.isDebugEnabled()) { logger.debug("[remove] on: " + this + " Profile[" + profileName + "]"); } ProfileObject profileObject = getProfile(profileName); if (profileObject != null) { // remove using object profileObject.profileRemove(invokeConcreteSbb); // close mbean if exists AbstractProfileMBeanImpl.close(profileTableName,profileName); return true; } else { return false; } } /** * * @param profileName * @throws IllegalArgumentException * @throws NullPointerException */ public static void validateProfileName(String profileName) throws IllegalArgumentException, NullPointerException { if (profileName == null) { throw new NullPointerException("ProfileName must not be null"); } if (profileName.length() == 0) { throw new IllegalArgumentException( "Profile name must not be empty, see section 10.2.4 of JSLEE 1.1 specs"); } for (int i = 0; i < profileName.length(); i++) { Character c = profileName.charAt(i); if (!(Character.isLetterOrDigit(c.charValue()) || (_UNICODE_RANGE_START <= c && c <= _UNICODE_RANGE_END))) { throw new IllegalArgumentException( "Profile name contains illegal character, name: " + profileName + ", at index: " + i); } } } /** * * @param profileTableName * @throws IllegalArgumentException * @throws NullPointerException */ public static void validateProfileTableName(String profileTableName) throws IllegalArgumentException, NullPointerException { if (profileTableName == null) { throw new NullPointerException("ProfileTableName must not be null"); } if (profileTableName.length() == 0) { throw new IllegalArgumentException( "ProfileTableName must not be empty, see section 10.2.4 of JSLEE 1.1 specs"); } for (int i = 0; i < profileTableName.length(); i++) { Character c = profileTableName.charAt(i); if (!((Character.isLetterOrDigit(c.charValue()) || (_UNICODE_RANGE_START <= c && c <= _UNICODE_RANGE_END)) && c != _UNICODE_SLASH)) { throw new IllegalArgumentException( "ProfileTableName contains illegal character, name: " + profileTableName + ", at index: " + i); } } } /** * * @return */ public ProfileEntity getDefaultProfileEntity() { return defaultProfileEntity; } /** * * @throws CreateException * @throws ProfileVerificationException */ public void createDefaultProfile() throws CreateException, ProfileVerificationException { if (logger.isDebugEnabled()) { logger.debug("Creating default profile for table "+profileTableName); } ProfileObject profileObject = transactionView.createProfile(null); profileObject.profileVerify(); this.defaultProfileEntity = profileObject.getProfileEntity(); } public ProfileObject createProfile(String newProfileName) throws TransactionRequiredLocalException, NullPointerException, SLEEException, ProfileAlreadyExistsException, CreateException { if (logger.isDebugEnabled()) { logger.debug("Adding profile with name " + newProfileName + " on table with name " + newProfileName); } validateProfileName(newProfileName); // switch the context classloader to the component cl ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( component.getClassLoader()); if (profileExists(newProfileName)) { throw new ProfileAlreadyExistsException("Profile with name '" + newProfileName + "' already exists in table '" + this.getProfileTableName() + "'"); } /* * FIXME afaik the default profile doesn't count, let it be till a test fails if (component.getDescriptor().isSingleProfile()) { throw new SLEEException( "Profile Specification indicates that this is single profile, can not create more than one: " + component); } */ return transactionView.createProfile(newProfileName); } catch (IllegalArgumentException e) { throw new SLEEException(e.getMessage(), e); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#findProfileByAttribute(java.lang.String, java.lang.Object) */ public ProfileLocalObject findProfileByAttribute(String attributeName, Object attributeValue) throws NullPointerException, IllegalArgumentException, TransactionRequiredLocalException, SLEEException { if (logger.isDebugEnabled()) { logger.debug("findProfileByAttribute( attributeName = "+attributeName+" , attributeValue = "+attributeValue+" )"); } Collection<ProfileLocalObject> plocs = findProfilesByAttribute(attributeName, attributeValue); if(plocs.size() == 0) { return null; } else { return plocs.iterator().next(); } } /* * (non-Javadoc) * @see javax.slee.profile.ProfileTable#findProfilesByAttribute(java.lang.String, java.lang.Object) */ public Collection<ProfileLocalObject> findProfilesByAttribute(String attributeName, Object attributeValue) throws NullPointerException, IllegalArgumentException, TransactionRequiredLocalException, SLEEException { if (logger.isDebugEnabled()) { logger.debug("findProfilesByAttribute( attributeName = "+attributeName+" , attributeValue = "+attributeValue+" )"); } sleeContainer.getTransactionManager().mandateTransaction(); // We get profile entities Collection<ProfileEntity> profileEntities = null; try { profileEntities = getProfileEntitiesByAttribute(attributeName, attributeValue, true); } catch (AttributeNotIndexedException e) { throw new SLEEException(e.getMessage(),e); } catch (UnrecognizedAttributeException e) { throw new IllegalArgumentException(e); } catch (AttributeTypeMismatchException e) { throw new IllegalArgumentException(e); } // We need ProfileLocalObjects ArrayList<ProfileLocalObject> plocs = new ArrayList<ProfileLocalObject>(); for(ProfileEntity profileEntity : profileEntities) { plocs.add(transactionView.getProfile(profileEntity).getProfileLocalObject()); } return Collections.unmodifiableCollection(plocs); } /* * (non-Javadoc) * @see org.mobicents.slee.container.profile.ProfileTableConcrete#getProfilesByAttribute(java.lang.String, java.lang.Object, boolean) */ public Collection<ProfileID> getProfilesByAttribute( String attributeName, Object attributeValue, boolean isSlee11) throws UnrecognizedAttributeException, AttributeNotIndexedException, AttributeTypeMismatchException, SLEEException { if (logger.isDebugEnabled()) { logger.debug("getProfilesByAttribute( attributeName = "+attributeName+" , attributeValue = "+attributeValue+" , isSlee11 = "+isSlee11+" )"); } // We get profile entities Collection<ProfileEntity> profileEntities = getProfileEntitiesByAttribute(attributeName, attributeValue, isSlee11); // We need ProfileIDs Collection<ProfileID> profileIDs = new ArrayList<ProfileID>(); for(ProfileEntity profileEntity : profileEntities) { profileIDs.add( new ProfileID(profileEntity.getTableName(), profileEntity.getProfileName()) ); } return Collections.unmodifiableCollection( profileIDs ); } /** * Retrieves the {@link ProfileEntity}s from the persistent store, matching specified attribute name and value type * @param attributeName * @param attributeValue * @param isSlee11 * @return * @throws UnrecognizedAttributeException * @throws AttributeNotIndexedException * @throws AttributeTypeMismatchException * @throws SLEEException */ private Collection<ProfileEntity> getProfileEntitiesByAttribute( String attributeName, Object attributeValue, boolean isSlee11) throws UnrecognizedAttributeException, AttributeNotIndexedException, AttributeTypeMismatchException, SLEEException { if (logger.isDebugEnabled()) { logger.debug("getProfileEntitiesByAttribute( attributeName = "+attributeName+" , attributeValue = "+attributeValue+" , isSlee11 = "+isSlee11+" )"); } ProfileAttribute profileAttribute = getProfileAttribute(attributeName, attributeValue); if (isSlee11) { // validate attr value type if (!ProfileAttribute.ALLOWED_PROFILE_ATTRIBUTE_TYPES.contains(attributeValue.getClass().getName())) { throw new AttributeTypeMismatchException(attributeValue.getClass()+" is not a valid profile attribute value type"); } } else { if (!profileAttribute.isIndex()) { throw new AttributeNotIndexedException(component.toString()+" defines an attribute named "+attributeName+" but not indexed"); } } return component.getProfileEntityFramework().findProfilesByAttribute(this.getProfileTableName(), profileAttribute, attributeValue); } /** * Retrieves the {@link ProfileAttribute} from the component, matching specified attribute name and value type * @param attributeName * @param attributeValue * @return * @throws NullPointerException * @throws UnrecognizedAttributeException * @throws AttributeTypeMismatchException */ private ProfileAttribute getProfileAttribute(String attributeName, Object attributeValue) throws NullPointerException, UnrecognizedAttributeException, AttributeTypeMismatchException { if (attributeName == null) { throw new NullPointerException("attribute name is null"); } if (attributeValue == null) { throw new NullPointerException("attribute value is null"); } ProfileAttribute profileAttribute = component.getProfileAttributes().get(attributeName); if (profileAttribute == null) { throw new UnrecognizedAttributeException(component.toString()+" does not defines an attribute named "+attributeName); } else { Class<?> allowedProfileAttributeType = profileAttribute.getNonPrimitiveType().isArray() ? profileAttribute.getNonPrimitiveType().getComponentType() : profileAttribute.getNonPrimitiveType() ; if (!allowedProfileAttributeType.getName().equals(attributeValue.getClass().getName())) { throw new AttributeTypeMismatchException(component.toString()+" defines an attribute named "+attributeName+" with value type "+profileAttribute.getType()+", the specified value is of type "+attributeValue.getClass()); } else { return profileAttribute; } } } private ObjectName getUsageMBeanName() throws IllegalArgumentException { try { return new ObjectName(ProfileTableUsageMBean.BASE_OBJECT_NAME + ',' + ProfileTableUsageMBean.PROFILE_TABLE_NAME_KEY + '=' + ObjectName.quote(profileTableName)); } catch (Exception e) { throw new IllegalArgumentException( "Failed to create MBean name, due to some system level error.", e); } } /** * Determines if profile is in back end storage == visible to other * compoenents than MBean, if null is passed as argumetn it must check for * any other than defualt? */ public boolean profileExists(String profileName) { boolean result = component.getProfileEntityFramework().findProfile(this.getProfileTableName(), profileName) != null; if (logger.isDebugEnabled()) { logger.debug("Profile named "+profileName+(result ? "" : " does not")+" exists on table named " + this.getProfileTableName()); } return result; } /** * Triggers remove operation on this profile table. * * @throws UnrecognizedProfileTableNameException */ public void remove() throws SLEEException { if (logger.isDebugEnabled()) { logger.debug("removeProfileTable: removing profileTable=" + profileTableName); } // remove the table profiles, at this stage they may use notification source, lets leave it. for (ProfileID profileID : getProfiles()) { // don't invoke the profile concrete object, to avoid evil profile lifecycle impls // that rollbacks tx, as Test1110251Test this.removeProfile(profileID.getProfileName(), false); } // remove default profile if (getDefaultProfileEntity() != null) { this.removeProfile(null, false); } // add action after commit to remove tracer and close uncommitted mbeans TransactionalAction commitAction = new TransactionalAction() { public void execute() { // remove notification sources for profile table TraceMBeanImpl traceMBeanImpl = sleeContainer.getTraceMBean(); traceMBeanImpl.deregisterNotificationSource(new ProfileTableNotification(profileTableName)); // close uncommitted mbeans closeUncommittedProfileMBeans(); } }; try { sleeContainer.getTransactionManager().addAfterCommitAction(commitAction); } catch (SystemException e) { throw new SLEEException(e.getMessage(),e); } endActivity(); // unregister mbean unregisterUsageMBean(); // remove object pool sleeContainer.getProfileObjectPoolManagement().removeObjectPool(this, sleeContainer.getTransactionManager()); } /** * * @param profileName * @return * @throws TransactionRequiredLocalException * @throws SLEEException */ public ProfileObject getProfile(String profileName) throws TransactionRequiredLocalException, SLEEException { return transactionView.getProfile(profileName); } /** * Use this method (and the object it returns) very carefully, if the profile entity is not from the current * transaction and changes are done, those won't be persisted * * @param profileEntity * @return * @throws TransactionRequiredLocalException * @throws SLEEException */ public ProfileObject getProfile(ProfileEntity profileEntity) throws TransactionRequiredLocalException, SLEEException { return transactionView.getProfile(profileEntity); } public String toString() { return " ProfileTableImpl ( table = " + this.profileTableName + " , component =" + component + " )"; } /** * * @param queryName * @param arguments * @return * @throws InvalidArgumentException * @throws AttributeTypeMismatchException * @throws UnrecognizedQueryNameException * @throws SLEEException * @throws NullPointerException */ public Collection<ProfileLocalObject> getProfilesByStaticQuery(String queryName, Object[] arguments) throws NullPointerException, SLEEException, UnrecognizedQueryNameException, AttributeTypeMismatchException, InvalidArgumentException { sleeContainer.getTransactionManager().mandateTransaction(); Collection<ProfileLocalObject> plocs = new ArrayList<ProfileLocalObject>(); for(ProfileEntity profileEntity : component.getProfileEntityFramework().getProfilesByStaticQuery( this.getProfileTableName(), queryName, arguments )) { plocs.add(transactionView.getProfile(profileEntity).getProfileLocalObject()); } return Collections.unmodifiableCollection(plocs); } // ACTIVITY related /** * */ public ProfileTableActivity getActivity() { return new ProfileTableActivityImpl(new ProfileTableActivityHandle(this.profileTableName)); } /** * * @return */ private ActivityContextHandle getActivityContextHandle() { return ActivityContextHandlerFactory .createProfileTableActivityContextHandle(new ProfileTableActivityHandle( profileTableName)); } /** * * @return */ public ActivityContext getActivityContext() { return sleeContainer.getActivityContextFactory().getActivityContext(getActivityContextHandle()); } /** * */ public void startActivity() { sleeContainer.getActivityContextFactory().createActivityContext(getActivityContextHandle()); } /** * */ public void endActivity() { ActivityContext ac = getActivityContext(); if (ac != null) { ac.endActivity(); } } // USAGE MBEAN RELATED /** * */ public void registerUsageMBean() { if (component.getUsageParametersInterface() != null) { // create resource usage mbean try { ObjectName objectName = this.getUsageMBeanName(); this.profileTableUsageMBean = new ProfileTableUsageMBeanImpl( this.profileTableName, component, sleeContainer); this.profileTableUsageMBean.setObjectName(objectName); sleeContainer.getMBeanServer().registerMBean( this.profileTableUsageMBean, objectName); // create default usage param set this.profileTableUsageMBean.createUsageParameterSet(); } catch (Throwable t) { if (this.profileTableUsageMBean != null) { this.profileTableUsageMBean.remove(); } throw new SLEEException( "Failed to create and register Table Usage MBean", t); } } } /** * */ public void unregisterUsageMBean() { if (this.profileTableUsageMBean != null) { try { this.profileTableUsageMBean.remove(); } catch (Throwable e) { throw new SLEEException(e.getMessage(),e); } } } // OPEN PROFILE MBEANS FOR PROFILES NOT CREATED /** * */ private final Set<AbstractProfileMBean> uncommittedProfileMBeans = new ConcurrentHashSet<AbstractProfileMBean>(); /** * * @param profileMBean */ public void addUncommittedProfileMBean(AbstractProfileMBean profileMBean) { uncommittedProfileMBeans.add(profileMBean); } /** * * @param profileMBean */ public void removeUncommittedProfileMBean(AbstractProfileMBean profileMBean) { uncommittedProfileMBeans.remove(profileMBean); } /** * */ private void closeUncommittedProfileMBeans() { // run it in a new thread to ensure no nested transactions Runnable r = new Runnable() { public void run() { for (AbstractProfileMBean profileMBean : uncommittedProfileMBeans) { if (logger.isDebugEnabled()) { logger.debug("Closing uncommitted mbean "+profileMBean); } // just rollback the profile creation, since in that use case there is a rollback action to unregister the bean try { profileMBean.restoreProfile(); } catch (Throwable e) { logger.error(e.getMessage(),e); } } } }; new Thread(r).start(); } public void setDefaultProfileEntity(ProfileEntity profileEntity) { this.defaultProfileEntity = profileEntity; } }