The largest Interview Solution Library on the web


« Previous | 1 | 2 | 3 | Next »

Entity beans


Introduction

The entity bean is one of two primary bean types: entity and session. The entity bean is used to represent data in the database. It provides an object-oriented interface to data that would normally be accessed by the EJB or some other back-end API. More than that, entity beans provide a component model that allows bean developers to focus their attention on the business logic of the bean, while the container takes care of managing persistence, transactions, and access control.

There are two basic kinds of entity beans: container-managed persistence (CMP) and bean-managed persistence (BMP). With CMP, the container manages the persistence of the entity bean. Vendor tools are used to map the entity fields to the database and absolutely no database access code is written in the bean class. With BMP, the entity bean contains database access code (usually EJB) and is responsible for reading and writing its own state to the database. BMP entities have a lot of help with this since the container will alert the bean as to when it's necessary to make an update or read its state from the database. The container can also handle any locking or transactions, so that the database maintains integrity.

Container-managed persistence

Container-managed persistence beans are the simplest for the bean developer to create and the most difficult for the EJB server to support. This is because all the logic for synchronizing the bean's state with the database is handled automatically by the container. This means that the bean developer doesn't need to write any data access logic, while the EJB server is supposed to take care of all the persistence needs automatically -- a tall order for any vendor. Most EJB vendors support automatic persistence to a relational database, but the level of support varies. Some provide very sophisticated object-to-relational mapping, while others are very limited.

In this panel, you will expand the CustomerBean developed earlier to a complete definition of a Container-managed persistence bean. In the panel on bean-managed persistence, you will modify the CustomerBean to manage its own persistence.

Bean class

An enterprise bean is a complete component that is made up of at least two interfaces and a bean implementation class. All these types will be presented and their meaning and application explained, starting with the bean class, which is defined below:

import javax.ejb.EntityBean;
public class CustomerBean implements EntityBean {
int customerID;
Address myAddress;
Name myName;
CreditCard myCreditCard;
// CREATION METHODS
public Integer ejbCreate(Integer id) {
customerID = id.intValue();
return null;
}
public void ejbPostCreate(Integer id) {
}
public Customer ejbCreate(Integer id, Name name) {
myName = name;
return ejbCreate(id);
}
public void ejbPostCreate(Integer id, Name name) {
}
// BUSINESS METHODS
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
public Address getAddress() {
return myAddress;
}
public void setAddress(Address address) {
myAddress = address;
}
public CreditCard getCreditCard() {
return myCreditCard;
}
public void setCreditCard(CreditCard card) {
myCreditCard = card;
}
// CALLBACK METHODS
public void setEntityContext(EntityContext cntx) {
}
public void unsetEntityContext() {
}
public void ejbLoad() {
}
public void ejbStore() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
}

This is a good example of a fairly simple CMP entity bean. Notice that there is no database access logic in the bean. This is because the EJB vendor provides tools for mapping the fields in the CustomerBean to the database. The CustomerBean class, for example, could be mapped to any database providing it contains data that is similar to the fields in the bean. In this case, the bean's instance fields are composed of a primitive int and simple dependent objects (Name, Address,and CreditCard) with their own attributes. Below are the definitions for these dependent objects:

// The Name class
public class Name implements Serializable {
public String lastName, firstName, middleName;
public Name(String lastName, String firstName,
String middleName) {
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
}
public Name() {}
}
// The Address class
public class Address implements Serializable {
public String street, city, state, zip;
public Address(String street, String city,
String state, String zip) {
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
}
public Address() {}
}
// The CreditCard class
public class CreditCard implements Serializable {
public String number, type, name;
public Date expDate;
public CreditCard(String number, String type,
String name, Date expDate) {
this.number = number;
this.type = type;
this.name = name;
this.expDate = expDate;
}
public CreditCard() {}
}

These fields are called container-managed fields because the container is responsible for synchronizing their state with the database; the container manages the fields. Container-managed fields can be any primitive data types or serializable type. This case uses both a primitive int (customerID) and serializable objects (Address, Name, CreditCard). To map the dependent objects to the database, a fairly sophisticated mapping tool would be needed. Not all fields in a bean are automatically container-managed fields; some may be just plain instance fields for the bean's transient use. A bean developer distinguishes container-managed fields from plain instance fields by indicating which fields are container-managed in the deployment descriptor.

The container-managed fields must have corresponding types (columns in RDBMS) in the database either directly or through object-relational mapping. The CustomerBean might, for example, map to a CUSTOMER table in the database that has the following definition:

CREATE TABLE CUSTOMER
{
id INTEGER PRIMARY KEY,
last_name CHAR(30),
first_name CHAR(20),
middle_name CHAR(20),
street CHAR(50),
city CHAR(20),
state CHAR(2),
zip CHAR(9),
credit_number CHAR(20),
credit_date DATE,
credit_name CHAR(20),
credit_type CHAR(10)
}

With container-managed persistence, the vendor must have some kind of proprietary tool that can map the bean's container-managed fields to their corresponding columns in a specific table, CUSTOMER in this case.

Once the bean's fields are mapped to the database, and the Customer bean is deployed, the container will manage creating records, loading records, updating records, and deleting records in the CUSTOMER table in response to methods invoked on the Customer bean's remote and home interfaces.

A subset (one or more) of the container-managed fields will also be identified as the bean's primary key. The primary key is the index or pointer to a unique record(s) in the database that makes up the state of the bean. In the case of the CustomerBean, the id field is the primary key field and will be used to locate the bean's data in the database. Primitive single field primary keys are represented as their corresponding object wrappers. The primary key of the Customer bean for example is a primitive int in the bean class, but to a bean's clients it will manifest itself as the java.lang.Integer type. Primary keys that are made up of several fields, called compound primary keys, will be represented by a special class defined by the bean developer. Primary keys are similar in concept to primary keys in a relational database -- actually when a relational database is used for persistence, they are often the same thing.

Home interface

To create a new instance of a CMP entity bean, and therefore insert data into the database, the create() method on the bean's home interface must be invoked. The Customer bean's home interface is defined by the CustomerHome interface; the definition is shown below:

public interface CustomerHome extends javax.ejb.EJBHome {
public Customer create(Integer customerNumber)
throws RemoteException,CreateException;
public Customer create(Integer customerNumber, Name name)
throws RemoteException,CreateException;
public Customer findByPrimaryKey(Integer customerNumber)
throws RemoteException, FinderException;
public Enumeration findByZipCode(int zipCode)
throws RemoteException, FinderException;
}

Below is an example of how the home interface would be used by an application client to create a new customer:

CustomerHome home = // Get a reference to the CustomerHome object
Name name = new Name("John", "W", "Smith");
Customer customer = home.create(new Integer(33), name);

A bean's home interface may declare zero or more create() methods, each of which must have corresponding ejbCreate() and ejbPostCreate() methods in the bean class. These creation methods are linked at run time, so that when a create() method is invoked on the home interface, the container delegates the invocation to the corresponding ejbCreate() and ejbPostCreate() methods on the bean class.

When the create() method on a home interface is invoked, the container delegates the create() method call to the bean instance's matching ejbCreate() method. The ejbCreate() methods are used to initialize the instance state before a record is inserted into the database. In this case, they initialize the customerID and Name fields. When the ejbCreate() method is finished (they return null in CMP) the container reads the container-managed fields and inserts a new record into the CUSTOMER table indexed by the primary key, in this case customerID as it maps to the CUSTOMER.ID column.

In EJB, an entity bean doesn't technically exist until after its data has been inserted into the database, which occurs during the ejbCreate() method. Once the data has been inserted, the entity bean exists and can access its own primary key and remote references, which isn't possible until after the ejbCreate() method completes and the data is in the database. If a bean needs to access its own primary key or remote reference after it's created, but before it services any business methods, it can do so in the ejbPostCreate() method. The ejbPostCreate() allows the bean to do any post-create processing before it begins serving client requests. For every ejbCreate() there must be a matching (matching arguments) ejbPostCreate() method.

The methods in the home interface that begin with "find" are called the find methods. These are used to query the EJB server for specific entity beans, based on the name of the method and arguments passed. Unfortunately, there is no standard query language defined for find methods, so each vendor will implement the find method differently. In CMP entity beans, the find methods are not implemented with matching methods in the bean class; containers implement them when the bean is deployed in a vendor specific manner. The deployer will use vendor specific tools to tell the container how a particular find method should behave. Some vendors will use object-relational mapping tools to define the behavior of a find method while others will simply require the deployer to enter the appropriate SQL command.

There are two basic kinds of find methods: single-entity and multi-entity. Single-entity find methods return a remote reference to the one specific entity bean that matches the find request. If no entity beans are found, the method throws an ObjectNotFoundException . Every entity bean must define the single-entity find method with the method name findByPrimaryKey(), which takes the bean's primary key type as an argument. (In the above example, you used the Integer type, which wrapped the int type of the id field in the bean class.) The multi-entity find methods return a collection ( Enumeration or Collection type) of entities that match the find request. If no entities are found, the multi-entity find returns an empty collection. (Note that an empty collection is not the same thing as a null reference.)

Remote interface

Every entity bean must define a remote interface in addition to the home interface. The remote interface defines the business methods of the entity bean. The following is the remote interface definition for the Customer bean:

import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer extends EJBObject {
public Name getName()
throws RemoteException;
public void setName(Name name)
throws RemoteException;
public Address getAddress()
throws RemoteException;
public void setAddress(Address address)
throws RemoteException;
public CreditCard getCreditCard()
throws RemoteException;
public void setCreditCard(CreditCard card)
throws RemoteException;
}

Below is an example of how a client application would use the remote interface to interact with a bean:

Customer customer = // ... obtain a remote reference to the bean
// get the customer's address
Address addr = customer.getAddress();
// change the zip code
addr.zip = "56777";
// update the customer's address
customer.setAddress(addr);

The business methods in the remote interface are delegated to the matching business methods in the bean instance. In the Customer bean, the business methods are all simple accessors and mutators, but they could be more complicated. In other words, an entity's business methods are not limited to reading and writing data; they can also perform tasks and computations.

If customers had, for example, a loyalty program that rewarded frequent use, there might be methods to upgrade membership in the program based on an accumulation of overnight stays. See below:

import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer extends EJBObject {
public MembershipLevel addNights(int nights_stayed)
throws RemoteException;
public MembershipLevel upgradeMembership()
throws RemoteException;
public MembershipLevel getMembershipLevel()
throws RemoteException;
... Simple accessor business methods go here
}

The addNights() and upgradeMembership() methods are more sophisticated than simple accessor methods. They apply business rules to change the membership level and go beyond reading and writing data.

Callback methods

The bean class defines create methods that match methods in the home interface and business methods that match methods in the remote interface. The bean class also implements a set of callback methods that allow the container to notify the bean of events in its life cycle. The callback methods are defined in the javax.ejb.EntityBean interface that is implemented by all entity beans, including the CustomerBean class. The EntityBean interface has the following definition. Notice that the bean class implements these methods.

public interface javax.ejb.EntityBean {
public void setEntityContext();
public void unsetEntityContext();
public void ejbLoad();
public void ejbStore();
public void ejbActivate();
public void ejbPassivate();
public void ejbRemove();
}

The setEntityContext() method provides the bean with an interface to the container called the EntityContext. The EntityContext interface contains methods for obtaining information about the context under which the bean is operating at any particular moment. The EntityContext interface is used to access security information about the caller; to determine the status of the current transaction or to force a transaction rollback; or to get a reference to the bean itself, its home, or its primary key. The EntityContext is set only once in the life of an entity bean instance, so its reference should be put into one of the bean instance's fields if it will be needed later.<
The Customer bean above doesn't use the EntityContext, but it could. For example, it could use the EntityContext to validate the caller's membership in a particular security role. Below is an example, where the EntityContext is used to validate that the caller is a Manager, the only role (security identity) that can set the credit card type on a customer to be a WorldWide card, the no-limit card of the super wealthy. (Customers with this card are automatically tagged for extra service.)

import javax.ejb.EntityBean;
public class CustomerBean implements EntityBean {
int customerID;
Address myAddress;
Name myName;
CreditCard myCreditCard;
EntityContext ejbContext;
// CALLBACK METHODS
public void setEntityContext(EntityContext cntx) {
ejbContext = cntx
}
public void unsetEntityContext() {
ejbContext = null;
}
// BUSINESS METHODS
public void setCreditCard(CreditCard card) {
if (card.type.equals("WorldWide"))
if (ejbContext.isCallerInRole("Manager"))
myCreditCard = card;
else
throw new SecurityException();
else
myCreditCard = card;
}
public CreditCard getCreditCard() {
return myCreditCard;
}
...
}

The unsetEntityContext() method is used at the end of the bean's life cycle -- before the instance is evicted from memory -- to dereference the EntityContext and perform any last-minute clean-up.

The ejbLoad() and ejbStore() methods in CMP entities are invoked when the entity bean's state is being synchronized with the database. The ejbLoad() is invoked just after the container has refreshed the bean container-managed fields with its state from the database. The ejbStore() method is invoked just before the container is about to write the bean container-managed fields to the database. These methods are used to modify data as it's being synchronized. This is common when the data stored in the database is different than the data used in the bean fields. The methods might be used, for example, to compress data before it is stored and decompress it when it is retrieved from the database.

In the Customer bean, the ejbLoad() and ejbStore() methods might be used to convert the dependent objects (Name, Address, CreditCard) to simple String objects and primitive types, if the EJB container is not sophisticated enough to map the dependent objects to the CUSTOMER table. Below is an example of how the bean might be modified:

import javax.ejb.EntityBean;
public class CustomerBean implements EntityBean {
//container-managed fields
int customerID;
String lastName;
String firstName;
String middleName;
...
// not-container-managed fields
Name myName;
Address myAddress;
CreditCard myCreditCard;
// BUSINESS METHODS
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
...
public void ejbLoad() {
if (myName == null)
myName = new Name();
myName.lastName = lastName;
myName.firstName = firstName;
myName.middleName = middleName;
...
}
public void ejbStore() {
lastName = myName.lastName;
firstName = myName.firstName;
middleName = myName.middleName;
...
}
}

The ejbPassivate() and ejbActivate() methods are invoked on the bean by the container just before the bean is passivated and just after the bean is activated, respectively. Passivation in entity beans means that the bean instance is disassociated with its remote reference so that the container can evict it from memory or reuse it. It's a resource conservation measure the container employs to reduce the number of instances in memory. A bean might be passivated if it hasn't been used for a while or as a normal operation performed by the container to maximize reuse of resources. Some containers will evict beans from memory, while others will reuse instances for other more active remote references. The ejbPassivate() and ejbActivate() methods provide the bean with a notification as to when it's about to be passivated (disassociated with the remote reference) or activated (associated with a remote reference).

Bean-managed persistence

The bean-managed persistence (BMP) enterprise bean manages synchronizing its state with the database as directed by the container. The bean uses a database API (usually EJB) to read and write its fields to the database, but the container tells it when to do each synchronization operation and manages the transactions for the bean automatically. Bean-managed persistence gives the bean developer the flexibility to perform persistence operations that are too complicated for the container or to use a data source that is not supported by the container -- custom and legacy databases, for example.

In this panel, you'll modify the CustomerBean class to be a BMP bean instead of a CMP bean. This modification will not impact the remote or home interface at all. In fact, you won't modify the original CustomerBean directly. Instead, you'll change it to bean-managed persistence by extending the bean and overriding the appropriate methods. Below is the definition of the class that will extend the Customer bean class to make it a BMP entity. In most cases, you will not extend a bean to make it BMP, you will just implement the bean as BMP directly. This strategy (extending the CMP bean) is done for two reasons: it allows the bean to be either a CMP or BMP bean; and it conveniently cuts down on the amount of code needed to display. Below is the definition of the BMP class that will be added to as this panel proceeds:

public class CustomerBean_BMP extends CustomerBean {
public void ejbLoad() {
// override implementation
}
public void ejbStore() {
// override implementation
}
public void ejbCreate() {
// override implementation
}
public void ejbRemove() {
// override implementation
}
private Connection getConnection() {
// new helper method
}
}

With BMP beans, the ejbLoad() and ejbStore() methods are used differently by the container and bean than was the case in CMP. In BMP, the ejbLoad() and ejbStore() methods contain code to read the bean's data from the database and to write changes to the database, respectively. These methods are called on the bean automatically, when the EJB server decides it's a good time to read or write data.

The CustomerBean_BMP bean manages its own persistence. In other words, the ejbLoad() and ejbStore() methods must include database access logic, so that the bean can load and store its data when the EJB container tells it to. The container will execute the ejbLoad() and ejbStore() methods automatically when appropriate.

The ejbLoad() method is usually invoked by the container at the beginning of a transaction, just before the container delegates a business method to the bean. The code below shows how to implement the ejbLoad() method using EJB:

import java.sql.Connection;
public class CustomerBean_BMP extends CustomerBean {
public void ejbLoad() {
Connection con;
try {
Integer primaryKey = (Integer)ejbContext.getPrimaryKey();
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT * FROM Customer " +
" WHERE customerID = " +
primaryKey.intValue());
ResultSet results = sqlStmt.executeQuery();
if (results.next()) {
// get the name information from the customer table
myName = new Name();
myName.first = results.getString("FIRST_NAME");
myName.last = results.getString("LAST_NAME");
myName.middle = results.getString("MIDDLE_NAME");
// get the address information from the customer table
myAddress = new Address();
myAddress.street = results.getString("STREET");
myAddress.city = results.getString("CITY");
myAddress.state = results.getString("STATE");
myAddress.zip = results.getInt("ZIP");
// get the credit card information from the customer table
myCreditCard = new CreditCard();
myCreditCard.number = results.getString("CREDIT_NUMBER");
myCreditCard.expDate = results.getString("CREDIT_DATE");
myCreditCard.type = results.getString("CREDIT_TYPE");
myAddress.name = results.getInt("CREDIT_NAME");
}
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
}

In the ejbLoad() method, use the ejbContext() reference to the bean's EntityContext to obtain the instance's primary key. This approach ensures that you use the correct index to the database. Obviously, the CustomerBean_BMP will need to use the inherited setEntityContext() and unsetEntityContext() methods as illustrated earlier.

The ejbStore() method is invoked by the container on the bean, at the end of a transaction, just before the container attempts to commit all changes to the database.

import java.sql.Connection;
public class CustomerBean_BMP extends CustomerBean {
public void ejbLoad() {
... read data from database
}
public void ejbStore() {
Connection con;
try {
Integer primaryKey = (Integer)ejbContext.getPrimaryKey();
con = this.getConnection();
PreparedStatement sqlPrep =
con.prepareStatement(
"UPDATE customer set " +
"last_name = ?, first_name = ?, middle_name = ?, " +
"street = ?, city = ?, state = ?, zip = ?, " +
"card_number = ?, card_date = ?, " +
"card_name = ?, card_name = ?, " +
"WHERE id = ?"
);
sqlPrep.setString(1,myName.last);
sqlPrep.setString(2,myName.first);
sqlPrep.setString(3,myName.middle);
sqlPrep.setString(4,myAddress.street);
sqlPrep.setString(5,myAddress.city);
sqlPrep.setString(6,myAddress.state);
sqlPrep.setString(7,myAddress.zip);
sqlPrep.setInt(8, myCreditCard.number);
sqlPrep.setString(9, myCreditCard.expDate);
sqlPrep.setString(10, myCreditCard.type);
sqlPrep.setString(11, myCreditCard.name);
sqlPrep.setInt(12,primaryKey.intValue());
sqlPrep.executeUpdate();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
}

In both the ejbLoad() and ejbStore() methods the bean synchronizes its own state with the database using EJB. If you studied the code carefully you may have noticed that the bean obtains its database connection from the mysterious this.getConnection() method invocation, a method yet to be implemented. The getConnection() method is not a standard EJB method; it's just a private helper method implemented to conceal the mechanics of obtaining a database connection. Below is the definition of the getConnection() method:

import java.sql.Connection;
public class CustomerBean_BMP extends CustomerBean {
public void ejbLoad() {
... read data from database
}
public void ejbStore() {
... write data to database
}
private Connection getConnection() throws SQLException {
InitialContext jndiContext = new InitialContext();
DataSource source = (DataSource)
jndiContext.lookup("java:comp/env/jdbc/myDatabase");
return source.getConnection();
}
}

Database connections are obtained from the container using a default JNDI context called the JNDI Environment Naming Context (ENC). The ENC provides access to transactional, pooled, EJB connections through the standard connection factory, the javax.sql.DataSource type.

In BMP, the ejbLoad() and ejbStore() methods are invoked by the container to synchronize the bean instance with data in the database. To insert and remove entities in the database, the ejbCreate() and ejbRemove() methods are implemented with similar database access logic. The ejbCreate() methods are implemented so that a new record is inserted into the database and the ejbRemove() methods are implemented so that the entity's data is deleted from the database. The ejbCreate() methods and the ejbRemove() method of a BMP entity are invoked by the container in response to invocations on their corresponding methods in the home and remote interfaces. The implementations of these methods are shown below:

public void ejbCreate(Integer id) {
this.customerID = id.intValue();
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("INSERT INTO customer id VALUES (" +
customerID + ")");
sqlStmt.executeUpdate();
return id;
}
catch(SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
public void ejbRemove() {
Integer primaryKey = (Integer)ejbContext.getPrimaryKey();
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("DELETE FROM customer WHERE id = "
primaryKey.intValue());
sqlStmt.executeUpdate();
}
catch(SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}

In BMP, the bean class is responsible for implementing the find methods defined in the home interface. For each find method defined in the home interface there must be corresponding ejbFind() method in the bean class. The ejbFind() methods locate the appropriate bean records in the database and return their primary keys to the container. The container converts the primary keys into bean references and returns them to the client. Below is an example implementation of the ejbFindByPrimaryKey() method in the CustomerBean_BMP class, which corresponds to the findByPrimaryKey() defined in the CustomerHome interface:

public Integer ejbFindByPrimaryKey(Integer primaryKey)
throws ObjectNotFoundException {
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT * FROM Customer " +
" WHERE customerID = " +
primaryKey.intValue());
ResultSet results = sqlStmt.executeQuery();
if (results.next())
return primaryKey;
else
throw ObjectNotFoundException();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}

Single-entity find methods like the one above return a single primary key or throw the ObjectNotFoundException if no matching record is located. Multi-entity find methods return a collection ( java.util.Enumeration or java.util.Collection ) of primary keys. The container converts the collection of primary keys into a collection of remote references, which are returned to the client. Below is an example of how the multi-entity ejbFindByZipCode() method, which corresponds to the findByZipCode() method defined in the CustomerHome interface, would be implemented in the CustomerBean_BMP class:

public Enumeration ejbFindByZipCode(int zipCode) {
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT id FROM Customer " +
" WHERE zip = " +zipCode);
ResultSet results = sqlStmt.executeQuery();
Vector keys = new Vector();
while(results.next()){
int id = results.getInt("id");
keys.addElement(new Integer(id));
}
return keys.elements();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}

If no matching bean records are found, an empty collection is returned to the container, which returns an empty collection to the client.

With the implementation of all these methods and a few minor changes to the bean's deployment descriptor, the CustomerBean_BMP is ready to be deployed as a BMP entity.
« Previous | 1 | 2 | 3 | Next »


copyright © 2014 - all rights riserved by javatechnologycenter.com