'From VisualWorks® NonCommercial, Release 5i.4 of August 9, 2001 on June 7, 2002 at 1:44:58 am'! Smalltalk.WirelessSuite defineClass: #WIPSMErrorCollector superclass: #{UI.Model} indexedType: #none private: false instanceVariableNames: 'aStream messages dialMessages ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMErrorCollector class methodsFor: 'private'! getIniParameter: aLine with: default | valueString| valueString := String new:256. Kernel.Undeclared.OSCall new getPrivateProfileString: 'EC' "asLPSTR" lpszEntry: aLine "asLPSTR" lpszDefault: default "asLPSTR" lpszReturnBuffer: valueString "asLPSTR" cbReturnBuffer: 256 lpszFilename: 'EC.INI' "asLPSTR". valueString:=valueString trimBlanks trimNull. ^valueString !WirelessSuite.WIPSMErrorCollector class methodsFor: 'instance creation'! new ^super new initialize! ! !WirelessSuite.WIPSMErrorCollector class methodsFor: 'public'! applicationError: anErrorString "Records an application type error and shuts down system." | collector hold | hold := anErrorString. collector := self new. collector notice: ((collector messages at: 31) bindWith: anErrorString) addLineDelimiters. collector getFullStats: ((self getIniParameter: 'appErrorDumpClass' with: 'AbtObservableObject') asClass). collector shutdown! cfsErrorOccured: anError exit: aBoolean "Records CFS errors." self new cfsErrorRecord: anError exit: aBoolean! dbConnectionError " Called when the connection to the database fails. Use the ec.ini file installed in the windows directory to set the file name for starting the db if necessary or desired." | runProgram collector file AbtProgramStarter | collector:=self new. (collector question: 33) ifTrue:[ file:=(self getIniParameter: 'dbstart' with: 'STARTDB.BAT'). runProgram:=AbtProgramStarter new programName:file. runProgram startProgram. ]. collector shutdown. ^collector dbmError: anError "Records database type errors." | collector hold AbtError | WIPSMConnectionManager databaseConnection rollbackExternal. WIPSMConnectionManager getLocal ifFalse: [^self dbRemoteConnectedError]. hold := anError. collector := self new. collector error: 30. collector getFullStats: AbtError. collector shutdown. dbRemoteConnectedError " Called when the connection to the database has dropped. " | collector AbtError | collector := self new. collector error: 35. collector getFullStats: AbtError. Processor activeProcess terminate dbRemoteConnectionError " Called when the connection to the database fails. " | collector AbtError | WIPSMConnectionManager disconnectRemoteConnection. "NON-ENT" collector := self new. collector error: 32. collector getFullStats: AbtError. Processor activeProcess terminate remoteDialConnectionError: rc "Records dial up connection problems." | returnCode collector error sp top AbtError System | (rc isKindOf: Number) ifFalse: [^self applicationError: rc printString]. rc < 600 ifTrue: [returnCode := 154] ifFalse: [rc = 600 ifTrue: [returnCode := 153] ifFalse: [returnCode := rc - 600]]. WIPSMConnectionManager disconnectRemoteConnection. "NON-ENT" collector := self new. error := AbtError in: WIPSMConnectionManager locus: 'remoteConnection' rc: rc. "NON-ENT" error errorText: (collector dialMessages at: returnCode). collector dialError: ('A problem occurred with the remote connection.\Error Message: %1\Please correct the problem, if possible, and try again.' bindWith: (collector dialMessages at: returnCode)) addLineDelimiters. System startUpClass outputWalkback: (collector dialMessages at: returnCode). System startUpClass cleanUpBeforeWalkback. collector getFullStats: AbtError. Processor activeProcess terminate "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMErrorCollector comment: 'This class as the name suggests provides functionality to collect and handle errors that might occur at various places in the Object-relational persistence framework. The errors include connection errors, database errors etc. Instance Variables: aStream represents a stream on which all the errors are written messages an array of various error messages that might occur while connecting or disconnecting from a database and other related errors. dialMessages an array of error messages that might occur only while dialing and establishing a remote connection Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7. '! !WirelessSuite.WIPSMErrorCollector methodsFor: 'testing'! testClass: anInstance loop: aNum "Test class of anInstance to prevent endless looping." anInstance isNil ifTrue: [^nil]. anInstance abtCanBeBoolean ifTrue: [^nil]. (anInstance isKindOf: Magnitude) ifTrue: [^nil]. anInstance isString ifTrue: [^nil]. (anInstance isKindOf: WIPSMAbstractProxy) ifTrue: [^nil]. (anInstance isKindOf: Collection) ifTrue: [^anInstance do: [:each | self testClass: each loop: aNum]]. anInstance isClass ifTrue: [^nil]. ((anInstance class name at: 1) = $A and: [(anInstance class name at: 2) = $b and: [(anInstance class name at: 3) = $t]]) ifTrue: [^nil]. self getInstanceValues: anInstance loop: aNum! ! !WirelessSuite.WIPSMErrorCollector methodsFor: 'initialize-release'! initialize "Initializes generic messages for error handler." self aStream: (WriteStream on: String new). self messages: ((Array new: 35) at: 1 put: 'System error: %1\The current operation could not be completed.\The application will now be terminated.'; at: 2 put: 'System error: %1\The current operation could not be completed.'; at: 3 put: 'File cannot be removed.'; at: 4 put: 'File does not exist.'; at: 5 put: 'Path does not exist.'; at: 6 put: 'Too many files open.'; at: 7 put: 'Low level I/O error.'; at: 8 put: 'Invalid function use or invalid argument.'; at: 9 put: 'Insufficient memory.'; at: 10 put: 'Path specified is invalid or not accessable.'; at: 11 put: 'Drive does not exist.'; at: 12 put: 'Directory cannot be removed.'; at: 13 put: 'Do Not Use!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'; at: 14 put: 'Cannot move file to a different drive.'; at: 15 put: 'There are no more files.'; at: 16 put: 'Path is read only or locked.'; at: 17 put: 'Device is not ready.'; at: 18 put: 'Invalid function use or invalid argument.'; at: 19 put: 'Data error - CRC.'; at: 20 put: 'Printer is out of paper.'; at: 21 put: 'Cannot read from the specified device.'; at: 22 put: 'Sharing violation.'; at: 23 put: 'Another process has a partial file lock.'; at: 24 put: 'The file already exists.'; at: 25 put: 'The parameter is invalid.'; at: 26 put: 'The filename, directory name, or volume name is incorrect.'; at: 27 put: 'The specified path is invalid.'; at: 28 put: 'Cannot create file when it already exists.'; at: 29 put: ' An ERROR has occured.\\Please click OK and wait while the system attempts to record the error.\\The specific error message will appear after the error is recorded.' addLineDelimiters; at: 30 put: ' A Database ERROR has occured.\\Please click OK and wait while the system attempts to record the error.\\Once the error has been recorded the application will be terminated.' addLineDelimiters; at: 31 put: 'An Application ERROR has occured.\\Error Message: %1\\Please click OK and wait while the system attempts to record the error.\\Once the error has been recorded the application will be terminated.'; at: 32 put: 'The connection to the remote database could not be established.\Please check the communication devices.' addLineDelimiters; at: 33 put: 'The connection to the database could not be established.\The application will have to be restarted after the database is started.\Would you like to try to start the database before leaving the application.?' addLineDelimiters; at: 34 put: 'Please wait while the error is recorded.'; at: 35 put: 'The connection to the remote database could not be maintained.\The error will be recorded and you will be able to search again.' addLineDelimiters; yourself)! ! !WirelessSuite.WIPSMErrorCollector methodsFor: 'action'! cfsErrorOccuredExit: msg exit: aBoolean "Processes CFS errors based on aBoolean." | errorMsg System | errorMsg := aBoolean ifTrue: [self messages at: 1] ifFalse: [self messages at: 2]. System errorMessage: (errorMsg bindWith: msg) addLineDelimiters. aBoolean ifTrue: [self shutdown]. ^nil cfsErrorRecord: anError exit: aBoolean "Translates platform error number user message." | err msg privBool EsError | self notice: (self messages at: 29). self getFullStats: EsError. privBool := aBoolean. privBool isNil ifTrue: [privBool := true]. msg := 'Unknown'. err := anError platformErrno. err = 1 ifTrue: [msg := self messages at: 3]. err = 2 ifTrue: [msg := self messages at: 4]. err = 3 ifTrue: [msg := self messages at: 5]. err = 4 ifTrue: [msg := self messages at: 6]. err = 5 ifTrue: [msg := self messages at: 7]. "Could also be a undocumented error number." err = 7 ifTrue: [msg := self messages at: 8]. err = 8 ifTrue: [msg := self messages at: 9]. err = 13 ifTrue: [msg := self messages at: 10]. err = 15 ifTrue: [msg := self messages at: 11]. err = 16 ifTrue: [msg := self messages at: 12]. err = 17 ifTrue: [msg := self messages at: 14]. err = 18 ifTrue: [msg := self messages at: 15]. err = 19 ifTrue: [msg := self messages at: 16]. err = 21 ifTrue: [msg := self messages at: 17]. err = 22 ifTrue: [msg := self messages at: 18]. err = 23 ifTrue: [msg := self messages at: 19]. err = 28 ifTrue: [msg := self messages at: 20]. err = 30 ifTrue: [msg := self messages at: 21]. err = 32 ifTrue: [msg := self messages at: 22]. err = 33 ifTrue: [msg := self messages at: 23]. err = 80 ifTrue: [msg := self messages at: 24]. err = 87 ifTrue: [msg := self messages at: 25]. err = 123 ifTrue: [msg := self messages at: 26]. err = 161 ifTrue: [msg := self messages at: 27]. err = 183 ifTrue: [msg := self messages at: 28]. self cfsErrorOccuredExit: msg exit: privBool. ^anError coreOfFullStats: aClass "Process sequence for getting error and system information." self getSystemInformation. self getSystemMemoryStats. self getInstances: aClass. aClass allSubclasses do: [:each | self getInstances: each]. self writeToFile! dialError: anObject "Displays dial up error messages" | message CwMessagePrompter | message := anObject isInteger ifTrue: [self dialMessages at: anObject] ifFalse: [anObject]. CwMessagePrompter errorMessage: message error: anInteger "Displays an error message to the user." | CwMessagePrompter | CwMessagePrompter errorMessage: (self messages at: anInteger) notice: aString "Informs user of an event." | System | System message: aString question: anObject "Prompts the user for a decision." | aString CwMessagePrompter | aString := anObject. anObject isInteger ifTrue: [aString := self messages at: anObject]. ^CwMessagePrompter confirm: aString shutdown "Disconnects any database connections using a empty error block to ensure disconnection is done. Requires a fix from IBM for database applications." | connections System | connections := AbtDbmSystem registeredConnections. connections do: [:each | each disconnectIfError: [:err | ]]. System startUpClass isRuntime ifTrue: [System exit] writeToFile "Writes collected information to installation directory in text file format." | fileStream myStream log newLog testForLog CfsWriteFileStream CfsReadFileStream | myStream := WriteStream on: (String new: 12). "Derive a unique file name." myStream nextPutAll: 'OD'; nextPutAll: ((Time microsecondClockValue + Date today asSeconds) / 33333) asInteger printString; nextPutAll: '.TXT'. fileStream := CfsWriteFileStream openEmpty: myStream contents. fileStream nextPutAll: self aStream contents. testForLog := CfsReadFileStream open: 'walkback.log'. log := testForLog isCfsError ifTrue: [testForLog] ifFalse: [(CfsReadFileStream open: 'walkback.log') upToEnd]. log isCfsError ifFalse: [fileStream nextPutAll: log]. newLog := CfsWriteFileStream openEmpty: 'walkback.log'. newLog flush. newLog close. newLog := nil. fileStream flush. fileStream close. fileStream := nil !WirelessSuite.WIPSMErrorCollector methodsFor: 'accessing'! aStream "Return the value of aStream." ^aStream! aStream: aWriteStream "Save the value of aStream." aStream := aWriteStream. " self signalEvent: #aStream with: aWriteStream"! dialMessages "Return the value of dialMessages." dialMessages isNil ifTrue: [dialMessages := self loadDialUpErrorMessages]. ^dialMessages! dialMessages: anArray "Save the value of dialMessages." dialMessages := anArray. " self signalEvent: #dialMessages with: anArray"! getFullStats: aClass "This method is not yet ent level code but is conditionalized to allow it to run just will not provide the wait message." | top yep | top := nil. "Replace AbstractWindowDefaultFormat with a GUI class to provide windowing message" "CwTopLevelShell all detect:[ :each | each userData parentPart isKindOf: AbstractWindowDefaultFormat] ifNone:[]." top isNil ifTrue: [self coreOfFullStats: aClass] ifFalse: [yep := top userData parentPart inProgressDialog. yep isNil ifFalse: [yep close. yep messageString: (self messages at: 34). yep open]. top userData parentPart execLongOperation: [self coreOfFullStats: aClass] message: (self messages at: 34) allowCancel: false showProgress: false]! getInstances: aClass | inst | inst := aClass allInstances. (self aStream) nextPutAll: aClass printString; tab; nextPutAll: inst size printString; cr. inst do: [:each | self getInstanceValues: each loop: 1]! getInstanceValues: anInstance loop: aNum | varNames attribCount varValue | aNum = 1 ifTrue: [1 to: aNum do: [:index | self aStream tab]. (self aStream) nextPutAll: anInstance printString; cr]. varNames := self inspectorVariables: anInstance. attribCount := 0. varNames do: [:each | attribCount := attribCount + 1. varValue := anInstance instVarAt: attribCount. 1 to: aNum do: [:index | self aStream tab]. varValue isNil ifTrue: [(self aStream) tab; nextPutAll: each name; tab; nextPutAll: nil printString; cr] ifFalse: [(self aStream) tab; nextPutAll: each name; tab; nextPutAll: varValue class printString; tab; nextPutAll: varValue printString; cr. self testClass: varValue loop: aNum + 1]]! getSystemInformation "Gets system information for error log." | System | aStream nextPutAll: 'System Information'; cr; tab; nextPutAll: 'CPU Architecture: '; nextPutAll: System cpuArchitecture; cr; tab; nextPutAll: 'OS Type: '; nextPutAll: System osType; cr; tab; nextPutAll: 'OS Version: '; nextPutAll: System osVersion; cr; tab; nextPutAll: 'VM Type: '; nextPutAll: System vmType; cr; tab; nextPutAll: 'VM Version: '; nextPutAll: System vmVersion; cr; tab; nextPutAll: 'VM Version ID: '; nextPutAll: System vmVersionId; cr; cr getSystemMemoryStats "Gets system information for error log." | System | aStream nextPutAll: 'System Memory Statistics'; cr; tab; nextPutAll: 'Available Free Memory in Fixed Space: '; nextPutAll: System availableFixedSpaceMemory printString; cr; tab; nextPutAll: 'Available Free Memory in the System: '; nextPutAll: System availableMemory printString; cr; tab; nextPutAll: 'Available Free Memory in New Space: '; nextPutAll: System availableNewSpaceMemory printString; cr; tab; nextPutAll: 'Available Free Memory in Old Space: '; nextPutAll: System availableOldSpaceMemory printString; cr; tab; nextPutAll: 'Total Memory Allocated by the VM: '; nextPutAll: System totalAllocatedMemory printString; cr; cr inspectorVariables: anInstance "Answer a collection containing variable references for all visible children of the receiver." | idx named indexed | idx := 0. named := anInstance basicClass allInstVarNames collect: [:name | idx := idx + 1. (WIPSMErrorInstVar new) object: anInstance; name: name; index: idx; yourself]. indexed := (1 to: anInstance basicSize) collect: [:i | (WIPSMErrorInstVar new) object: anInstance; name: i printString; index: i; yourself]. ^named , indexed! loadDialUpErrorMessages "Error messages for dial up dll." ^(Array new: 154) at: 1 put: 'The port handle is invalid.'; at: 2 put: 'The port is already open.'; at: 3 put: 'Caller''s buffer is too small.'; at: 4 put: 'Wrong information specified.'; at: 5 put: 'Cannot set port information.'; at: 6 put: 'The port is not connected.'; at: 7 put: 'The event is invalid.'; at: 8 put: 'The device does not exist.'; at: 9 put: 'The device type does not exist.'; at: 10 put: 'The buffer is invalid.'; at: 11 put: 'The route is not available.'; at: 12 put: 'The route is not allocated.'; at: 13 put: 'Invalid compression specified.'; at: 14 put: 'Out of buffers.'; at: 15 put: 'The port was not found.'; at: 16 put: 'An asynchronous request is pending.'; at: 17 put: 'The port or device is already disconnecting.'; at: 18 put: 'The port is not open.'; at: 19 put: 'The port is disconnected.'; at: 20 put: 'There are no endpoints.'; at: 21 put: 'Cannot open the phone book file.'; at: 22 put: 'Cannot load the phone book file.'; at: 23 put: 'Cannot find the phone book entry.'; at: 24 put: 'Cannot write the phone book file.'; at: 25 put: 'Invalid information found in the phone book file.'; at: 26 put: 'Cannot load a string.'; at: 27 put: 'Cannot find key.'; at: 28 put: 'The port was disconnected.'; at: 29 put: 'The port was disconnected by the remote machine.'; at: 30 put: 'The port was disconnected due to hardware failure.'; at: 31 put: 'The port was disconnected by the user.'; at: 32 put: 'The structure size is incorrect.'; at: 33 put: 'The port is already in use or is not configured for Remote Access dial out.'; at: 34 put: 'Cannot register your computer on on the remote network.'; at: 35 put: 'Unknown error.'; at: 36 put: 'The wrong device is attached to the port.'; at: 37 put: 'The string could not be converted.'; at: 38 put: 'The request has timed out.'; at: 39 put: 'No asynchronous net available.'; at: 40 put: 'A NetBIOS error has occurred.'; at: 41 put: 'The server cannot allocate NetBIOS resources needed to support the client.'; at: 42 put: 'One of your NetBIOS names is already registered on the remote network.'; at: 43 put: 'A network adapter at the server failed.'; at: 44 put: 'You will not receive network message popups.'; at: 45 put: 'Internal authentication error.'; at: 46 put: 'The account is not permitted to logon at this time of day.'; at: 47 put: 'The account is disabled.'; at: 48 put: 'The password has expired.'; at: 49 put: 'The account does not have Remote Access permission.'; at: 50 put: 'The Remote Access server is not responding.'; at: 51 put: 'Your or other connecting device has reported an error.'; at: 52 put: 'Unrecognized response from the device.'; at: 53 put: 'A macro required by the device was not found in the device. INF file section.'; at: 54 put: 'A command or response in the device .INF file section refers to an undefined macro.'; at: 55 put: 'The macro was not found in the device .INF file secion.'; at: 56 put: 'The macro in the device .INF file section contains an undefined macro.'; at: 57 put: 'The device .INF file could not be opened.'; at: 58 put: 'The device name in the device .INF or media .INI file is too long.'; at: 59 put: 'The media .INI file refers to an unknown device name.'; at: 60 put: 'The device .INF file contains no responses for the command.'; at: 61 put: 'The device .INF file is missing a command.'; at: 62 put: 'Attempted to set a macro not listed in device .INF file section.'; at: 63 put: 'The media .INI file refers to an unknown device type.'; at: 64 put: 'Cannot allocate memory.'; at: 65 put: 'The port is not configured for Remote Access.'; at: 66 put: 'Your or other connecting device is not functioning.'; at: 67 put: 'Cannot read the media .INI file.'; at: 68 put: 'The connection dropped.'; at: 69 put: 'The usage parameter in the media .INI file is invalid.'; at: 70 put: 'Cannot read the section name from the media .INI file.'; at: 71 put: 'Cannot read the device type from the media .INI file.'; at: 72 put: 'Cannot read the device name from the media .INI file.'; at: 73 put: 'Cannot read the usage from the media .INI file.'; at: 74 put: 'Cannot read the maximum connection BPS rate from the media .INI file.'; at: 75 put: 'Cannot read the maximum carrier BPS rate from the media .INI file.'; at: 76 put: 'The line is busy.'; at: 77 put: 'A person answered instead of a modem.'; at: 78 put: 'There is no answer.'; at: 79 put: 'Cannot detect carrier.'; at: 80 put: 'There is no dial tone.'; at: 81 put: 'General error reported by device.'; at: 82 put: 'ERROR_WRITING_SECTIONNAME'; at: 83 put: 'ERROR_WRITING_DEVICETYPE'; at: 84 put: 'ERROR_WRITING_DEVICENAME'; at: 85 put: 'ERROR_WRITING_MAXCONNECTBPS'; at: 86 put: 'ERROR_WRITING_MAXCARRIERBPS'; at: 87 put: 'ERROR_WRITING_USAGE'; at: 88 put: 'ERROR_WRITING_DEFAULTOFF'; at: 89 put: 'ERROR_READING_DEFAULTOFF'; at: 90 put: 'ERROR_EMPTY_INI_FILE'; at: 91 put: 'Access denied because username and/or password is invalid on the domain.'; at: 92 put: 'Hardware failure in port or attached device.'; at: 93 put: 'ERROR_NOT_BINARY_MACRO'; at: 94 put: 'ERROR_DCB_NOT_FOUND'; at: 95 put: 'ERROR_STATE_MACHINES_NOT_STARTED'; at: 96 put: 'ERROR_STATE_MACHINES_ALREADY_STARTED'; at: 97 put: 'ERROR_PARTIAL_RESPONSE_LOOPING'; at: 98 put: 'A response keyname in the device .INF file is not in the expected format.'; at: 99 put: 'The device response caused buffer overflow.'; at: 100 put: 'The expanded command in the device .INF file is too long.'; at: 101 put: 'The device moved to a BPS rate not supported by the COM driver.'; at: 102 put: 'Device response received when none expected.'; at: 103 put: 'ERROR_INTERACTIVE_MODE'; at: 104 put: 'ERROR_BAD_CALLBACK_NUMBER'; at: 105 put: 'ERROR_INVALID_AUTH_STATE'; at: 106 put: 'ERROR_WRITING_INITBPS'; at: 107 put: 'X.25 diagnostic indication.'; at: 108 put: 'The account has expired.'; at: 109 put: 'Error changing password on domain. The password may be too short or may match a previously used password.'; at: 110 put: 'Serial overrun errors were detected while communicating with your modem.'; at: 111 put: 'RasMan initialization failure. Check the event log.'; at: 112 put: 'Biplex port initializing. Wait a few seconds and redial.'; at: 113 put: 'No active ISDN lines are available.'; at: 114 put: 'No ISDN channels are available to make the call.'; at: 115 put: 'Too many errors occured because of poor phone line quality.'; at: 116 put: 'The Remote Access IP configuration is unusable.'; at: 117 put: 'No IP addresses are available in the static pool of Remote Access IP addresses.'; at: 118 put: 'Timed out waiting for a valid response from the remote PPP peer.'; at: 119 put: 'PPP terminated by remote machine.'; at: 120 put: 'No PPP control protocols configured.'; at: 121 put: 'Remote PPP peer is not responding.'; at: 122 put: 'The PPP packet is invalid.'; at: 123 put: 'The phone number including prefix and suffix is too long.'; at: 124 put: 'The IPX protocol cannot dial-out on the port because the machine is an IPX router.'; at: 125 put: 'The IPX protocol cannot dial-in on the port because the IPX router is not installed.'; at: 126 put: 'The IPX protocol cannot be used for dial-out on more than one port at a time.'; at: 127 put: 'Cannot access TCPCFG.DLL.'; at: 128 put: 'Cannot find an IP adapter bound to Remote Access.'; at: 129 put: 'SLIP cannot be used unless the IP protocol is installed.'; at: 130 put: 'Computer registration is not complete.'; at: 131 put: 'The protocol is not configured.'; at: 132 put: 'The PPP negotiation is not converging.'; at: 133 put: 'The PPP control protocol for this network protocol is not available on the server.'; at: 134 put: 'The PPP link control protocol terminated.'; at: 135 put: 'The requested address was rejected by the server.'; at: 136 put: 'The remote computer terminated the control protocol.'; at: 137 put: 'Loopback detected.'; at: 138 put: 'The server did not assign an address.'; at: 139 put: 'The authentication protocol required by the remote server cannot use the Windows NT encrypted password. Redial, entering the password explicitly.'; at: 140 put: 'Invalid TAPI configuration.'; at: 141 put: 'The local computer does not support encryption.'; at: 142 put: 'The remote server does not support encryption.'; at: 143 put: 'The remote server requires encryption.'; at: 144 put: 'Cannot use the IPX network number assigned by remote server. Check the event log.'; at: 145 put: 'ERROR_INVALID_SMM'; at: 146 put: 'ERROR_SMM_UNINITIALIZED'; at: 147 put: 'ERROR_NO_MAC_FOR_PORT'; at: 148 put: 'ERROR_SMM_TIMEOUT'; at: 149 put: 'ERROR_BAD_PHONE_NUMBER'; at: 150 put: 'ERROR_WRONG_MODULE'; at: 151 put: 'Invalid callback number. Only the characters 0 to 9, T, P, W, (, ), -, @, and space are allowed in the number. '; at: 152 put: 'A syntax error was encountered while processing a script.'; at: 153 put: 'An operation is pending.'; at: 154 put: 'A general error has occurred.'; yourself! messages "Return the value of messages." ^messages! ! Smalltalk.WirelessSuite defineClass: #WIPSMErrorInstVar superclass: #{Core.Object} indexedType: #none private: false instanceVariableNames: 'allowChanges object name index ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMErrorInstVar comment: 'This class has not yet been commented. The comment should state the purpose of the class, what messages are subclassResponsibility, and the type and purpose of each instance and class variable. The comment should also explain any unobvious aspects of the implementation. Instance Variables: allowChanges a boolean indicating whether or not to allow changes as part of the Error Instance variable object a variable simply representing acting as a placeholder for an object as part of the Error Instance variable name a String variable representing the name of an object as part of the Error Instance variable index a numeric variable indicating index within a collection as part of the Error Instance variable Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7. '! !WirelessSuite.WIPSMErrorInstVar methodsFor: 'accessing'! allowChanges "Return the value of allowChanges." ^allowChanges! allowChanges: aBoolean "Save the value of allowChanges." allowChanges := aBoolean. " self signalEvent: #allowChanges with: aBoolean"! index "Return the value of index." ^index! index: anInteger "Save the value of index." index := anInteger. " self signalEvent: #index with: anInteger"! name "Return the value of name." ^name! name: aString "Save the value of name." name := aString. " self signalEvent: #name with: aString"! object "Return the value of object." ^object! object: anObject "Save the value of object." object := anObject. " self signalEvent: #object with: anObject"! ! Smalltalk.WirelessSuite defineClass: #WIPSMAbstractProxy superclass: #{UI.Model} indexedType: #none private: false instanceVariableNames: 'parentObject owningObject ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMAbstractProxy comment: 'This abstract class provides the basic framework for implementing the Proxy behavorial pattern and providing a placeholder for other objects created within the Persistence framework that might require one. It is especially useful for holding certain WIPSMPersistentObjects that may be composed of other such objects. Instance Variables: parentObject represents the parent object of this object owningObject represents the object that owns this object Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7. '! !WirelessSuite.WIPSMAbstractProxy methodsFor: 'delegating'! doesNotUnderstand: aMessage ^self subclassResponsibility! ! !WirelessSuite.WIPSMAbstractProxy methodsFor: 'accessing'! materialize "Answer that it is the responsibility of subclasses to materialize an object of type aClass for the proxy mechanism to use." ^self subclassResponsibility! owningObject "Return the value of owningObject." ^owningObject! owningObject: anObject "Save the value of owningObject." owningObject := anObject. " self signalEvent: #owningObject with: anObject"! parentObject "Return the value of parentObject." ^parentObject! parentObject: anObject "Save the value of parentObject." parentObject := anObject. " self signalEvent: #parentObject with: anObject"! ! Smalltalk.WirelessSuite defineClass: #WIPSMOIDManager superclass: #{Core.Object} indexedType: #none private: false instanceVariableNames: 'increment highKey currentKey lowKey keySize classReferenceDictionary ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMOIDManager defineSharedVariable: #SingletonInstance private: false constant: false category: 'As yet unclassified' initializer: nil! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMOIDManager class methodsFor: 'instance creation'! instance "Answer an instance of the Manager. If one is not running, one is instantiated." SingletonInstance isNil ifTrue: [SingletonInstance := super new initialize]. ^SingletonInstance! new "This class is a singletonInstance." ^self shouldNotImplement! ! !WirelessSuite.WIPSMOIDManager class methodsFor: 'public'! flush "Releases the singleton instance" SingletonInstance := nil! ! !WirelessSuite.WIPSMOIDManager class methodsFor: 'accessing'! getKey "Retrieves the new key" ^self instance getKey! siteKey "Return the value of siteKey. When using distributed databases the key is prefixed by this number to indicate which database the key was generated from. This also prevents from having to block numbers be each database. Each database uses a sequential number provide unique number for its rows. This method should be expanded accommodate you particular implementation." ^1234567! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMOIDManager comment: 'This class functions as an Object Identification manager providing functionality to assign unique identifiers to different Persistent objects that are created within the framework. It generates unique identifiers by making use of different key values and also registers classes of different objects that need to be persisted. It also implements the Singleton Pattern as only one instance of the WIPSMOIDManager can exist at any given time. Instance Variables: increment - represents the value by which a key value has to be incremented highKey - represents maximum value of key currentKey - represents value of current key lowKey - represents the minimum value of a key keySize - represents size of a key value classReferenceDictionary - represents a dictionary for resolving class names and table names Shared Variables: SingletonInstance represents whether or not an instance of this Class already exists Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7.'! !WirelessSuite.WIPSMOIDManager methodsFor: 'private accessing'! currentKey "Return the value of currentKey. This maintains the last key used by the Persistence Layer. The readKey method increments this number by one when a key is requested by the Persistence Layer" ^currentKey! currentKey: aNumber "Save the value of currentKey." currentKey := aNumber! getKey "This is the method to retrieve a key. The attributes are checked to see if a number needs to be read from the table or just increment the attribute of this Singleton instance by one and return the value." self currentKey = 0 ifTrue: [self readKey]. self currentKey = self highKey ifTrue: [self readKey. ^self currentKey]. self currentKey: self currentKey + 1. ^self currentKey! highKey "Return the value of highKey. This stores the number written back to the table when the table was queried to get the next number. When the current key reaches this number a new block is queried from the table." ^highKey! highKey: aNumber "Save the value of highKey." highKey := aNumber.! increment "Return the value of increment. This can be set by the application at startup or if not it is set to 10). This determines the size of the block of numbers retrieved from the table where the last key used by any user is stored." increment isNil ifTrue: [increment := 10]. ^increment! increment: anInteger "Save the value of increment." increment := anInteger.! keySize "Return the value of keySize. This can be set by the application at startup or if not it is set to 100000000). This determines the size of the key value." keySize isNil ifTrue: [keySize := 100000000]. ^keySize! keySize: aNumber "Save the value of aNumber." keySize := aNumber.! lowKey "Return the value of lowKey." ^lowKey! lowKey: aNumber "Save the value of lowKey." lowKey := aNumber.! readKey "Generate a unique key value for use in storing an object in the database." | newKey aMaxKey prep answer session connection row | WIPSMPersistentObject beginTransaction. connection := WIPSMPersistentObject databaseConnection. answer := (session := connection getSession) prepare: 'SELECT NUM_SEQ FROM SEQUENCE'; execute; answer. row := answer next. row == nil ifTrue:[aMaxKey := 1] ifFalse:[aMaxKey := row at:1.]. connection disconnect. newKey := aMaxKey + self increment. WIPSMPersistentObject executeSql: 'UPDATE SEQUENCE SET NUM_SEQ = ' , newKey printString. WIPSMPersistentObject endTransaction. prep := self class siteKey * self keySize. self lowKey: prep + aMaxKey. self currentKey: prep + aMaxKey. self highKey: prep + (newKey - 1). ^nil! ! !WirelessSuite.WIPSMOIDManager methodsFor: 'initialize-release'! initialize "Initializes a new instance." self lowKey: 0. self currentKey: 0. self highKey: 0. self initializeKeyNames.! initializeKeyNames self initializeKeyNames: WIPSMConnectionManager databaseConnection! initializeKeyNames: aDBConnection | collection | collection := self rowsFromDB: 'SELECT * FROM NICKNAME' connection: aDBConnection. classReferenceDictionary := Dictionary new. collection do: [:each | classReferenceDictionary at: (each at:1) put: (each at:2)].! ! !WirelessSuite.WIPSMOIDManager methodsFor: 'public'! classReferenceDictionary ^classReferenceDictionary isNil ifTrue: [self initializeKeyNames] ifFalse: [classReferenceDictionary]! getKeyFor: aClass | anStream | self classReferenceDictionary at: aClass dBNickName ifAbsent: [self registerClass: aClass]. anStream := WriteStream on: String new. anStream nextPutAll: aClass dBNickName. anStream nextPutAll: self getKey asInteger printString. ^anStream contents! insertionStatement: aClass | aStream | aStream := WriteStream on: String new. aStream nextPutAll: 'INSERT INTO NICKNAME'; nextPutAll: ' ( NICKNAME, CLASSNAME) VALUES ('; nextPutAll: ( WIPSMTypeConverter prepForSql: aClass dBNickName); nextPut: $,; nextPutAll: ( WIPSMTypeConverter prepForSql: aClass name); nextPutAll: ')'. ^aStream contents! instantiatorClassFor: anOID | aKey aString | aKey := anOID leftString: 3. aString := self classReferenceDictionary at: aKey ifAbsent: [self halt "Do you initialize the WIPSMOIDManager??"]. ^Smalltalk at: aString asSymbol! registerClass: aClass self classReferenceDictionary at: aClass dBNickName put: aClass name. WIPSMPersistentObject executeSql: (self insertionStatement: aClass)! rowsFromDB: sqlStatement connection: aDBConnection | answer session rows | answer := (session := aDBConnection getSession) prepare: sqlStatement; execute; answer. rows := OrderedCollection new. [answer atEnd] whileFalse: [ | row | row := answer next. rows add: row ]. aDBConnection disconnect. ^rows.! ! Smalltalk.WirelessSuite defineClass: #WIPSMTypeConverter superclass: #{Core.Object} indexedType: #none private: false instanceVariableNames: '' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMTypeConverter class methodsFor: 'accessing'! databaseConnection ^WIPSMConnectionManager databaseConnection.! ! !WirelessSuite.WIPSMTypeConverter class methodsFor: 'action'! prepForSql: anObject "Prepares an object into the correct db format to be placed on a stream. Each objects attribute is tested for what type it is and the appropriate format is returned to be placed on the stream for the SQL Code. This provides the Persistence Layer a common format for data types. The default date format is assumed to be (Locale current lcTime dFmt: '%m/%d/%Y'). If you do not want to use a full date format the date formatting should be changed to match your database. " anObject isNil ifTrue: [^'NULL']. anObject isString ifTrue: [anObject isEmpty ifTrue: [^'NULL'] ifFalse: [^anObject trimBlanks printString]]. anObject isReal ifTrue: [^anObject printString]. anObject abtCanBeDate ifTrue: [^anObject printString printString]. anObject abtCanBeBoolean ifTrue: [anObject ifTrue: [^'T' printString] ifFalse: [^'F' printString]]. anObject abtCanBeTime ifTrue: [^self databaseConnection databaseMgr sQLStringForTime: anObject]. (anObject isKindOf: WIPSMPersistentObject) ifTrue: [anObject objectIdentifier isNil ifTrue: [^'NULL'] ifFalse: [^anObject objectIdentifier printString]]! ! !WirelessSuite.WIPSMTypeConverter class methodsFor: 'converting'! convertToBooleanFalse: aString "Returns a boolean from a string character. False is the default value. The string is tested to see if a valid value can be derived from aString. If a value that does not match any criteria that is passed in a default value is assigned. This could indicate corrupted data or that no one has ever supplied the information." ^'t' = aString asString trimBlanks asLowercase! convertToBooleanTrue: aString "Returns a boolean from a string character. True is the default value. The string is tested to see if a valid value can be derived from aString. If a value that does not match any criteria is passed in a default value is assigned. This could indicate corrupted data or that no one has ever supplied the information." ^('f' = aString asString trimBlanks asLowercase) not! convertToNumber: aNumber "Returns a number from a db number . Default returns zero. The string is tested to see if a valid value can be derived from aString. If a value that does not match any criteria is passed in a default value is assigned. This could indicate corrupted data or that no one has ever supplied the information." ^aNumber isInteger ifTrue: [aNumber asInteger] ifFalse: [0]! convertToString: aString "Returns a string from a db string or character. Default returns a new string. The string is tested to see if a valid value can be derived from aString. If a value that does not match any criteria that is passed in a default value is assigned. This could indicate corrupted data or that no one has ever supplied the information." ^aString asString trimBlanks! convertToUpperString: aString "Returns an upper case string. This used the asString method to convert the string to a valid string format and then forces the string to upper case. This ensure that attributes that are to always be formatted upper case are upper case when the object receives them. " ^(self convertToString: aString) asUppercase! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMTypeConverter comment: 'This class provides functionality for converting a value from one type to another. It is used within the Persistence pattern for converting the values of different elements of the persistent objects from the type in which they reside in the database to the type that is relevant in the objects. Instance Variables: None Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7.'! Smalltalk.WirelessSuite defineClass: #WIPSMTableManager superclass: #{UI.Model} indexedType: #none private: false instanceVariableNames: 'localTables remoteTables ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMTableManager defineSharedVariable: #SingletonInstance private: false constant: false category: 'As yet unclassified' initializer: nil! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMTableManager class methodsFor: 'instance creation'! instance "Answer an instance of the Manager. If one is not running, one is instantiated. self instance initialize" SingletonInstance isNil ifTrue: [SingletonInstance := super new initialize]. ^SingletonInstance! new "This class is a singletonInstance." ^self shouldNotImplement! ! !WirelessSuite.WIPSMTableManager class methodsFor: 'accessing'! getTable: aString "Retrieves the table mapping for aString" ^self instance table: (aString ).! ! !WirelessSuite.WIPSMTableManager class methodsFor: 'action'! flush "Releases the singleton instance" SingletonInstance := nil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMTableManager comment: 'This class provides functionality for managing different tables related to the different kinds of Persistence objects that are created within the framework. It implements the Singleton pattern as only one instance of WIPSMTableManager can exist at a point of time. Instance Variables: localTables a dictionary of tables residing in a local database remoteTables a dictionary of tables residing in a remote database Shared Variables: SingletonInstance This variable indicates whether or not an instance of this class exists. Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7.'! !WirelessSuite.WIPSMTableManager methodsFor: 'initialize-release'! initialize self localTables: self iniLocal. self remoteTables: self iniRemote.! ! !WirelessSuite.WIPSMTableManager methodsFor: 'private'! iniRemote "This dictionary contains the names of the tables at the remote machine. The key values for the local and remote values should be the same. The physical table name or view, is then the value stored at the key." ^(Dictionary new: 2) at: 'EXAMPLE' put: 'EXAMPLE_V01'; at: 'ADDRESS' put: 'ADD_EXP_V01'; yourself "After changes execute below line." "WIPSMTableManager flush"! localTables "Return the value of localTables." ^localTables! localTables: aDictionary "Save the value of localTables." localTables := aDictionary. " self signalEvent: #localTables with: aDictionary"! remoteTables "Return the value of remoteTables." ^remoteTables! remoteTables: aDictionary "Save the value of remoteTables." remoteTables := aDictionary. " self signalEvent: #remoteTables with: aDictionary"! ! !WirelessSuite.WIPSMTableManager methodsFor: 'accessing'! iniLocal "This dictionary contains the names of the tables at the local machine. The key values for the local and remote values should be the same. The physical table name or view, is then the value stored at the key." ^(Dictionary new: 2) at: 'ACCOUNTHISTORY' put: 'ACCOUNTHISTORY'; at: 'ORDERS' put: 'ORDERS'; at: 'BILLINGATTRIBUTES' put: 'BILLINGATTRIBUTES'; at: 'CURRENTUSAGE' put: 'CURRENTUSAGE'; at: 'CUSTOMERS' put: 'CUSTOMERS'; at: 'NICKNAME' put: 'NICKNAME'; at: 'PREVCUSTOMERS' put: 'PREVCUSTOMERS'; at: 'RATEDOBJECTS' put: 'RATEDOBJECTS'; at: 'RATINGRULES' put: 'RATINGRULES'; at: 'SEQUENCE' put: 'SEQUENCE'; at: 'SERVICEPLANS' put: 'SERVICEPLANS'; at: 'SERVICES' put: 'SERVICES'; at: 'TAXRATES' put: 'TAXRATES'; yourself "After changes execute below line." "WIPSMTableManager flush"! table: aString "This is the method called by the Persistence Layer when a table name is needed. Astring is passed in to identify which key to return. The local variable in WIPSMConnectionManager is checked to see which dictionary should be accessed to return the table name to the Persistence Layer." ^WIPSMConnectionManager local ifTrue: [self localTables at: aString] ifFalse: [self remoteTables at: aString]! ! Smalltalk.WirelessSuite defineClass: #WIPSMConnectionManager superclass: #{Core.Object} indexedType: #none private: false instanceVariableNames: '' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMConnectionManager defineSharedVariable: #Local private: false constant: false category: 'As yet unclassified' initializer: nil! WirelessSuite.WIPSMConnectionManager defineSharedVariable: #DatabaseName private: false constant: false category: 'As yet unclassified' initializer: nil! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMConnectionManager class methodsFor: 'private-accessing'! local "Defaults the local variable to true." Local isNil ifTrue: [Local := true]. ^Local! local: aBoolean "Assigns the local variable." Local:=aBoolean! ! !WirelessSuite.WIPSMConnectionManager class methodsFor: 'connecting'! databaseConnection "Checks the local variable to see which connection to make or test for and returns the connection." ^self getLocal ifTrue: [self localConnection: self databaseName] ifFalse: [self remoteConnection]! disconnectAllConnections | connections | connections := AbtDbmSystem registeredConnections. connections do: [:each | each disconnectIfError: [:error | ]] disconnectLocalDatabase "Disconnect the from the Remote Database. Provides an error block of nil so if the connection has already failed it can still be disconnected. It also resets the local variable only look at local tables." | dataBaseConnection | dataBaseConnection := AbtDbmSystem activeDatabaseConnectionWithAlias: 'Example'. dataBaseConnection isNil ifFalse: [dataBaseConnection disconnectIfError: [:error | ]]. dataBaseConnection := nil. self getLocal: true! disconnectRemoteConnection "Drops the remote connection."! disconnectRemoteDatabase "Disconnect the from the Remote Database."! localConnection "Answer a connection to the WIPSMPersistentObject. The alias name is checked to see if a connection is already open, if not one is created with error blocks to trap errors that occur during connection and while the connection is open. It must be noted that it is important to create a threaded Oracle connection or else multiple simultaneous accesses won't be possible." | connection | connection := OracleThreadedConnection new. connection username: 'scott'; password: 'tiger'; environment: 'WIPSM'. connection connect. ^connection! localConnection: aDatabaseName "Answer a connection to the WIPSMPersistentObject. The alias name is checked to see if a connection is already open, if not one is created with error blocks to trap errors that occur during connection and while the connection is open. It must be noted that it is important to create a threaded Oracle connection or else multiple simultaneous accesses won't be possible." | connection | connection := OracleThreadedConnection new. connection username: 'scott'; password: 'tiger'; environment: 'WIPSM'. connection connect. ^connection! remoteConnection "Answers the connection to the remote db."! ! !WirelessSuite.WIPSMConnectionManager class methodsFor: 'accessing'! databaseName ^DatabaseName! databaseName: anObject DatabaseName := anObject! getLocal "Return the value of local. This identifies that the database being accessed is the local table and not the remote table. This attribute could be a character or integer value if more than two databases need to be accessed." ^self local.! getLocal: aBoolean "Sets the local variable" ^self local: aBoolean! ! !WirelessSuite.WIPSMConnectionManager class methodsFor: 'action'! connectCentralServer "Provides the connection media to the remote db and initiates the connection."! flush "Releases the local variable" Local := nil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMConnectionManager comment: 'This class provides the necessary infrastructure that is required to connect to a database that might be local to this machine or be residing on a remote machine. The class provides a scalable framework which allows for easy modifications to change from one database to another by clearly seperating the connection steps from the application model. Instance Variables: Shared Variables: DatabaseName represents name of the Database to which to connect Local a boolean value representing whether or not the Database is on Local Host machine Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7. '! Smalltalk.WirelessSuite defineClass: #WIPSMPersistentObject superclass: #{UI.Model} indexedType: #none private: false instanceVariableNames: 'objectIdentifier isPersisted isChanged owningObject ' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'instance creation'! new ^(super new) initialize; yourself! ! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'private-action'! beginTransaction "Tell Persistence Mechanism that a Transaction is beginning." self databaseConnection beginExternal. "self databaseConnection beginUnitOfWorkIfError: [ self databaseConnection rollbackUnitOfWork ]"! buildSqlStatement: aString ^self subclassResponsibility! endTransaction "Tell Persistence Mechanism that a Transaction is ending." self databaseConnection commitExternal.! executeSql: aSqlStatement "Execute a sql statement using an custom error block in runtime or use the default debugger during development. A sql statement is passed in from the domain object. This is where the majority of the CRUD is handed to the Database Components. If an error occurs the error block will invoke the error collector and prevent the debugger from appearing. In a development environment the default error block is used to allow the debugger to appear.." | answer session | answer := (session := self databaseConnection getSession) prepare: aSqlStatement; execute.! rollBackTransaction "Tell Persistence Mechanism that a Transaction is being rolled back." self databaseConnection rollbackExternal! ! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'private-accessing'! read: aSearchString "A string is passed in that the domain objects selectionClause method builds from the instance that calls it. The resultSet method in the WIPSMPersistanceObject receives the string and build the sql statement and fires the statement through the Database components. The returned collection (resultSet) is then iterated over and each row is passed to the initialize: method in the Domain object. The instance returned from the initialize: method (Mapping Attributes) is added to a collection and the collection is returned." | aCollection answerSet columnNames | columnNames := OrderedCollection new. aCollection := OrderedCollection new. answerSet := self resultSet: aSearchString. (answerSet isNil) ifTrue:[ ^nil ] ifFalse:[(answerSet columnDescriptions) do: [:aColumn | columnNames add:(aColumn name)]. answerSet do: [:aRow | aCollection add: (self new initialize: aRow using:columnNames)]. ^aCollection ].! resultSet: aString "Returns a resultSet (a resultSet is a collection of rows of data) using a custom error block if in runtime or default debugger if in development. This method receives aString from the domain objects selectionClause method. This string is then passed into the domain objects buildSqlStatement where the sql statement is built. This statement is then passed to Database Components which, in turn return a result set. This is a particular implementation for IBM's VisualAge for Smalltalk for reads only. The other CRUD operations are invoked with the sqlExecute method. The dataBaseConnection method is fired to obtain the connection to which the sql statement will be passed. Should an error occur in runtime the debugger is left in place and if the image is a runtime image then the error collector in invoked by the error block" | answer session | answer := (session := self databaseConnection getSession) prepare: (self buildSqlStatement: aString); execute; answer. ^answer.! table ^self subclassResponsibility! ! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'testing'! canInstantiate: anOID ^((anOID indexOfSubCollection: self dBNickName startingAt: 1) = 0) not! ! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'public'! dBNickName ^self subclassResponsibility! instanceFromDatabaseIdentified: anOID | aClass | aClass := WIPSMOIDManager instance instantiatorClassFor: anOID. ^(aClass read: 'ID_OBJ=' , (WIPSMTypeConverter prepForSql: anOID)) first! loadAll "Answer a collection of ALL WIPSMPersistentObjects for the class used. This is the method the user will call to create instances for all rows in a table for an object ( complex or simple ). This is useful for populating drop down lists" ^self read: nil! ! !WirelessSuite.WIPSMPersistentObject class methodsFor: 'connecting'! databaseConnection "Call to the WIPSMConnectionManager to get proper db connection. WIPSMConnectionManger then decides which connection it will return to the WIPSMPersistenceObject. WIPSMConnectionManager makes this decision based on the local attribute." ^WIPSMConnectionManager databaseConnection! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! WirelessSuite.WIPSMPersistentObject comment: 'This class provides the necessary framework for any object that might require to be persisted in a relational database. By providing for the basic framework that would be required to store, update, retrieve objects from a relational database it goes a long way in resolving the impedance mismatch that exists between the Object-Oriented and the Relation worlds. Instance Variables: objectIdentifier - represents a unique identifier that identifies this object isPersisted - a boolean value that represents whether or not an object has been persisted isChanged - a boolean value that represents whether or not an object has been changed owningObject - represents the identifier of the object owning the current object in case of Composite Objects which are not present in this project but may be present in real life scenarios. Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7.'! !WirelessSuite.WIPSMPersistentObject methodsFor: 'instance creation'! basicCreate "Creates a db row from a simple object. Based on the isPersisted attribute the Persistence layer has decided to generate the SQL Code for an insert. The insertRowSql method (in each domain object ) builds the sql statement and the Database Components fire the sql statement. If the statement is successful (if not the error handling will take control) the isPersisted flag is set to true. This prevents subquesent saves from writing the same data twice if no changes have occurred ." self class executeSql: self insertRowSql. isPersisted := true! create "Execute the sql insert statement for a simple object. Over write in the subclass if more check or statements are needed. This method is generally called from inside a transaction (TransactionManager) and provides the insert function of CRUD" self saveComponentIfDirty. self basicCreate! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'initialize-release'! initialize "Important: If a subclass overrides initialize, it is important for it to still call super initialize to set up the isPersisted and isChanged instance variables. These attributes identify the object is from or has been written to the database (isPersisted) and whether or not the object's attributes have been changed (isChanged). This provides and easy way to allow the Persistence Layer to decide to save the object or not, with an insert or an update sql statement." self isPersisted: false. self isChanged: false.! initialize: aRow using:columnNames ^self subclassResponsibility! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'private-accessing'! isChanged "Return the value of isChanged." ^isChanged! isChanged: aBoolean "Save the value of isChanged." isChanged := aBoolean. "self signalEvent: #isChanged with: aBoolean"! isPersisted "Return the value of isPersisted." ^isPersisted! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'private-action'! assignUniqueKey "Get a unique key for a new db row. The objectIdentifier is tested to ensure an objectIdentifier does not exist already. This could occur if a record is retrieved from another database and to be saved locally" self objectIdentifier: ((self objectIdentifier isNil or: [self objectIdentifier = 0]) ifTrue: [WIPSMOIDManager instance getKeyFor: self class] ifFalse: [self objectIdentifier]). ^self objectIdentifier! basicDelete "Deletes a row from the db for a simple object. Here the sql statement is build and sent to the Database Components using the objectIdentifier as the unique database key to delete the row from the table. The table name is returned from the domain object whose table method calls to the Table Manager to retrieve the correct table name for the connection and domain object." self class executeSql: 'DELETE FROM ' , self class table , ' WHERE ID_OBJ=' , objectIdentifier printString! basicUpdate "Updates a row for a simple object. This also check to make sure that self is not a proxy. If it is a proxy then the object and not been materialized and does not require an update. The objects isChanged attribute is checked to see that it requires an update. Once passed these checks the update sql statement is build by the domain object and passed to the Database Components for execution." (self isKindOf: WIPSMAbstractProxy) ifTrue: [^nil]. isChanged ifTrue: [self class executeSql: self updateRowSql]! deleteAsTransaction "Delete self from the database without Transaction Management." self isPersisted ifTrue: [self basicDelete]. ^nil! insertRowSql ^self subclassResponsibility! makeClean "Indicates that the object does not need to be saved to the db or that no changes have affected the object" self isChanged: false! makeDirty "Indicates an object needs to be saved to the db. This method can be called from the 'setter' method as in the example above." self isChanged: true! saveAsTransaction "Save self to the database without Transaction Management. This should be called only from inside an active transaction. This is useful for complex object in which the entire object should be save or rejected if a failure occurs." self isPersisted ifTrue: [self update] ifFalse: [self create]. self makeClean! saveComponentIfDirty "The default is to do nothing. Overwrite in the subclass if the object has components."! selectionClause "Answer a string representation of selection criteria based on the contents of the instance. This is basically a where condition for a sql select statement. Each class has a selectionClause method that determines what selection criteria can be used with the object it is located in." | aStream | aStream := WriteStream on: String new. self objectIdentifier isNil ifFalse: [aStream nextPutAll: 'ID_OBJ='; nextPutAll: (self typeConverter prepForSql: self objectIdentifier). ^aStream contents]. self owningObject isNil ifFalse: [aStream nextPutAll: 'ID_OBJ_OWN= '; nextPutAll: (self typeConverter prepForSql: self owningObject)]. ^aStream contents! update "Executes the sql update for a simple object. Overwrite in the subclass if more check or statements are needed. This method is generally called from inside a transaction (TransactionManager) and provides the update function of CRUD." self saveComponentIfDirty. self basicUpdate! updateRowSql ^self subclassResponsibility! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'accessing'! componentsFromDatabase | aCollection AbtQuerySpec | aCollection := OrderedCollection new. (self class databaseConnection resultTableFromQuerySpec: (AbtQuerySpec new statement: self componentsQuerySource)) do: [:aRow | (aCollection contains: [:each | each = (aRow at: 'ID_OBJ2')]) ifFalse: [aCollection add: (aRow at: 'ID_OBJ2')]]. ^aCollection componentsQuerySource | aStream | aStream := WriteStream on: String new. aStream nextPutAll: 'SELECT ID_OBJ2 FROM OBJLNKTB WHERE ID_OBJ1='; nextPutAll: (self typeConverter prepForSql: self objectIdentifier). ^aStream contents !WirelessSuite.WIPSMPersistentObject methodsFor: 'testing'! isRelationStoredFor: anObject ^self componentsFromDatabase contains: [:each | each = anObject objectIdentifier]! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'action'! addComponentRelationship: anObject | aStream | (self objectIdentifier isNil or: [anObject objectIdentifier isNil]) ifTrue: [^self]. (self isRelationStoredFor: anObject) ifTrue: [^self]. aStream := WriteStream on: String new. aStream nextPutAll: 'INSERT INTO '; nextPutAll: 'OBJLNKTB'; nextPutAll: ' (ID_OBJ1, ID_OBJ2 ) VALUES ('; nextPutAll: (self typeConverter prepForSql: self objectIdentifier); nextPut: $,; nextPutAll: (self typeConverter prepForSql: anObject objectIdentifier); nextPutAll: ')'. self class executeSql: aStream contents! delete "Delete self from the database. This is the method the user will call to delete an instance from the database. A transaction is started and closed after the instance (complex or simple) has been completely removed from the database." self class beginTransaction. self deleteAsTransaction. self class endTransaction. self isPersisted: false! load "Answer a subclass of WIPSMPersistentObject that matches self. If zero, nil is returned otherwise first is returned. This will call to the loadAllLike method which will query the database and load into instances all records that match the attributes of self. Each domain object provides the necessary method (i.e. WIPSMName>>selectionClause) to build a where clause for the SQL Code. In the case of duplicate data only the first occurance is returned." | oc | oc := self loadAllLike. ^oc isEmpty ifTrue: [nil] ifFalse: [oc first]! loadAllLike "Answer a collection of subclasses of WIPSMPersistentObjects that match self. This method is called by the above load and can be called when you want a collection of all instances (records) that match self. The selectionClause method in the Domain object is called to prepare the WHERE clause for the read method in the WIPSMPersistenceObject. This takes the resultSet (from WIPSMPersistenceObject) and pass the rows into the Domain object initialize: method (Mapping Attributes) which returns a instance with the data values from the database." ^self class read: self selectionClause! removeComponentRelationship: anObject | aStream | (self objectIdentifier isNil or: [anObject objectIdentifier isNil]) ifTrue: [^self]. aStream := WriteStream on: String new. aStream nextPutAll: 'DELETE FROM OBJLNKTB'; cr. aStream nextPutAll: 'WHERE ID_OBJ1='. aStream nextPutAll: self objectIdentifier printString. aStream nextPutAll: ' AND ID_OBJ2='. aStream nextPutAll: anObject objectIdentifier printString. self class executeSql: aStream contents save "Save self to the database. This is the method the user will call to save an instance to the database. A transaction is started and closed after the instance (complex or simple) has been completely sent to the database. This can be a new instance ( for a insert call) or an instance that may or may not need to be written back to the database ( for an update call)." self class beginTransaction. self saveAsTransaction. self class endTransaction! ! !WirelessSuite.WIPSMPersistentObject methodsFor: 'private-accesing'! isPersisted: aBoolean "Save the value of isPersisted." isPersisted := aBoolean. "self signalEvent: #isPersisted with: aBoolean"! objectIdentifier "Return the value of objectIdentifier. This is the unique identifier for the object as well as the key in the database. Objects that are to be loaded from the database can us this key for reading from or writing to the database. CRUD operations then have a unique single column key to uniquely identify the row for an update or delete operation." ^objectIdentifier! objectIdentifier: aNumber "Save the value of objectIdentifier." objectIdentifier := aNumber. " self signalEvent: #objectIdentifier with: aNumber"! owningObject "Return the value of owningObject. This is the unique key for the parent object or the parent object itself. When reading records from the database in a one to one or a one to many relation the owning object identifier can be referenced by the SQL Code and retrieve the child records for the parent." ^owningObject! owningObject: anObject "Save the value of owningObject." owningObject := anObject. " self signalEvent: #owningObject with: anObject"! typeConverter "This determines which class is responsible for converting (translating) types between tables types and object types. This method could be expanded to determine at run time which database is in use or which class to use for the conversions" ^WIPSMTypeConverter! ! Smalltalk.WirelessSuite defineClass: #WIPSMProxy superclass: #{WirelessSuite.WIPSMAbstractProxy} indexedType: #none private: false instanceVariableNames: '' classInstanceVariableNames: '' imports: '' category: 'Persistence Pattern'! WirelessSuite.WIPSMProxy comment: 'This is a subclass of WIPSMAbstractProxy class which implements the Proxy behavorial pattern and provides a placeholder for other objects created within the Persistence framework that might require one. It is especially useful for holding certain WIPSMPersistentObjects that may be composed of other such objects. Instance Variables: None Note: Although Persistence pattern is a reuse based on framework written by Joseph Yoder and Prof. Ralph Johnson for Visualage, it had to be reworked at different places to make it work with Visualworks 5i.4, and Oracle 8.1.7.'! !WirelessSuite.WIPSMProxy methodsFor: 'action'! materialize | object | object := (WIPSMOIDManager instance instantiatorClassFor: self owningObject) instanceFromDatabaseIdentified: self owningObject. self become: object. ^object! ! !WirelessSuite.WIPSMProxy methodsFor: 'delegating'! doesNotUnderstand: aMessage | object | object := self materialize. ^aMessage sendTo: object! !