= Release Notes CDO 3.0 =

This document elaborates on the '''new and noteworthy''' enhancements in CDO 3.0.<br>

__TOC__

Alternatively you can query Bugzilla directly to list
* [https://bugs.eclipse.org/bugs/buglist.cgi?field0-0-0=flagtypes.name;bug_severity=enhancement;resolution=FIXED;classification=Modeling;query_based_on=NEEDS_DOC;query_format=advanced;bug_status=RESOLVED;version=3.0;type0-0-0=notequals;value0-0-0=documentation%2B;component=CDO;component=Net4j;product=EMF;known_name=NEEDS_DOC Enhancements not described in this document]
* [https://bugs.eclipse.org/bugs/buglist.cgi?bug_severity=enhancement;resolution=FIXED;classification=Modeling;query_based_on=NEEDS_DOC;query_format=advanced;bug_status=RESOLVED;version=3.0;component=CDO;component=Net4j;product=EMF;known_name=NEEDS_DOC All enhancements]
* [https://bugs.eclipse.org/bugs/buglist.cgi?bug_severity=blocker;bug_severity=critical;bug_severity=major;bug_severity=normal;bug_severity=minor;bug_severity=trivial;resolution=FIXED;classification=Modeling;query_based_on=NEEDS_DOC;query_format=advanced;bug_status=RESOLVED;version=3.0;component=CDO;component=Net4j;product=EMF;known_name=NEEDS_DOC All bugs]

To get a complete overview about all API changes since version 2.0, breaking or compatible, you can
* browse the [api-report.html HTML report of the API comparison]
* use the [api-report.xml XML report of the API comparison]

== User Interface ==

=== CDO Explorer Perspective has been added ===

A new perspective has been added to let the user have a overall picture of the UI features the framework provides.
It serves as an entry point to experiment with the main concepts of the framework.

[[Image:cdo_explorer_perspective.png.png|600px]]

=== CDO Remote Sessions View has been added ===

This view allows browsing active sessions in an specific repository and even perform communication with those.

[[Image:remote_sessions_1.png]]

=== Common Navigator Framework Integration ===

Now you may use a CDO model repository through a workspace project, allowing you to explore the repository:

* Manage your resource tree: Open/create/delete/rename new resources or folders. You can also import an XMIResource into the repository, or export a resource from the repository to an XMIResource.
* Manage registered packages: Visualize packages registered in the repository, explore the ecore model, and add new packages.
* Manage branches: Create new branches, and switch your visualization and switch branches, import or export resources.

[[Image:cnf_integration_1.png]]

[[Image:cnf_integration_2.png]]

=== Legacy Mode Support ===

You enable the new legacy model support through the CDO Sessions view, by enabling it
in an opened session. Any view/transaction opened thereafter will be able to deal with EPackages that do not extend CDOObject.

[[Image:legacy_support.png]]

=== Branching Support ===

CDO Sessions view now allows the user to switch the target branch of a CDOView. This way you can
explore the state of a repository in certain branch.

[[Image:branch_support.png]]

=== Dawn - Rise of the collaborative UI ===

CDO now provides collaborative support for GMF editors over a CDO model repository with a new sub-component called ''Dawn''. Dawn allows the storage of a GMF editor�s model data using CDO. It enables GMF editors to be used in a real-time shared editing scenario.

With a generated OSGi fragment an existing GMF editor can be connected to the Dawn runtime which provides the automatic synchronization of the editor as well as conflict detection and conflict handling mechanisms. More information about Dawn is provided on [http://wiki.eclipse.org/Dawn website].

[[Image:Dawn_collaborative_work_conflict.png|800px]]

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=308232 308232].

== Core Framework ==

=== Branching and merging is now supported ===

Branches form an explicit, navigable tree in CDO. It is managed by a session's CDOBranchManager, which also emits CDOBranchCreatedEvent for local and remote branch creation.
<pre>
    CDOBranch mainBranch = session.getBranchManager().getMainBranch();
    CDOBranch team1Branch = mainBranch.createBranch("team1", timeStamp);

    CDOTransaction transaction = session.openTransaction(team1Branch);
    CDOView view = session.openView(team1Branch, team1Branch.getBase().getTimeStamp());
    view.setBranchPoint(mainBranch.getHead());
</pre>
<br>
Merging is also supported through the new merge(CDOBranchPoint source, CDOMerger merger) method in CDOTransaction. There are several default implementations of the CDOMerger interface provided. An interactive merger will be provided soon.

[[Image:MergeConcepts.png|480px]]

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=270716 270716].

=== Offline clones are now supported ===

You need to setup a normal repository as the master. Then you setup a second one via CDOServerUtil.createOfflineClone(String, IStore, Map<String, String>, IRepositorySynchronizer). Depending on the configuration of the synchronizer that you have to pass in  there are certain restrictions on the store type of the clone repo. The clone is a repository by itself and can be embedded it into a client or made available via TCP transport to many clients. So, it can also be used the clone like a "group server" to serve a subset of the overall clients. That speeds up reads but slows down writes a little bit, as commits are "write-through".

[[Image:OfflineOverview.png|480px]]

For the clone creation you need a repository synchronizer. Use CDOServerUtil.createRepositorySynchronizer(CDOSessionConfigurationFactory). The synchronizer maintains a session to the master and tries to re-establish that session if the connection breaks down. See: IRepositorySynchronizer.setRetryInterval(int)

A clone repository has a state, one of OFFLINE, SYNCING, ONLINE. Reads are always served from the clone. commits are write-through if the clone is ONLINE. If the clone is not ONLINE, a commit from a client will create a new "local" branch (or offline branch) on the fly. Your client app will always have to check the CDOCommitInfo that is returned from tx.commit(). The branch in the info can be different from the branch of the local tx. In that case it usually makes sense to switch the local tx to the newly created offline branch and adjust the UI accordingly. You will also have to persist that branch info locally, so that you can use that branch after client restarts.

After an OFFLINE phase of the clone repo the synchronizer enters an intermediary SYNCING state and fetches new changesets. After syncing everything the clone transitions back to ONLINE. now your app can merge up from the offline branch and commit the result to the master (write-through).

There are 2 different replication mechanisms in the synchronizer:

* The normal one analyzes the original changesets in the master, transports them over the network (much like a commit) and really calls a faked commit on the clone. So the original commits are really recreated. This mechanism currently just requires the two stores to use the same CDOID *type*, e.g. MEMStore and DBStore would be possible at the same time, one in the master, one in the clone. The drawback is that it is quite slow, especially if you have lots of small commits.

* We provided a "raw replication" mode in the synchronizer. That one streams the store content directly from one store to the clone store on a low level. It's extremely fast but requires the two stores to be identical. We could replicate 200MB in less then 15 seconds over the network.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=256936 256936].

=== Increased server reliability though backup repositories ===

Reusing the basic mechanism of clone repositories there is now support for operating several backup repositories. In this scenario both master server and backup server(s) must be started as FailoverParticipants.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=203167 203167].

=== Legacy models are now supported ===

Legacy models from a CDO perspective are those that have not been regenerated to support all CDO features natively.
These models are now supported by CDO, although they do not support lazy loading and unloading of their instances.
The legacy mode has to be enabled explicitely for new transactions/views:

<pre>
    // Enable legacy support from now on for all views that will be opened by this thread
    CDOUtil.setLegacyModeDefault(true);

    CDOView view = session.openView();
    System.out.println("Legacy model support: " + view.isLegacyModeEnabled());
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=247226 247226].

=== EMaps are now supported for DynamicCDOObject ===

Using EMaps on DynamicCDOObjects is now supported.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=305723 305723].

=== The transient modifier of an EStructuralFeature can now be overridden by annotation ===

Sometimes you want to use transient/persistent modifiers for particular structural features in
CDO repositories that differ from the normal (XML) behaviour. The following annotation constants have
been added to EMFUtil:

<pre>
      public static final String CDO_ANNOTATION_SOURCE = "http://www.eclipse.org/emf/CDO";
      public static final String CDO_ANNOTATION_KEY_PERSISTENT = "persistent";
</pre>
<br>
Their literals can be used in an Ecore model definition as follows:

<pre>
      <eClassifiers xsi:type="ecore:EClass" name="Product1">
        <eStructuralFeatures xsi:type="ecore:EAttribute" name="description" eType="..." transient="true">
          <eAnnotations source="http://www.eclipse.org/emf/CDO">
            <details key="persistent" value="true"/>
          </eAnnotations>
        </eStructuralFeatures>
      </eClassifiers>
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=290990 290990].

=== The session API to determine or wait for remote updates ===

A getLastUpdateTime() method has been added to CDOSession. With the new waitForUpdate() methods
in CDOSession you can, e.g., synchronize local views and transactions.

<pre>
    final CDOCommitInfo commitInfo = transaction.commit();

    new Thread()
    {
      @Override
      public void run()
      {
        session.waitForUpdate(commitInfo.getTimeStamp());
      }
    }.start();
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=283947 283947].

=== New session option for receiving more detailed commit infos ===

A session can now be configured to receive full revision deltas instead of just invalidation keys.

<pre>
    session.options().setPassiveUpdateMode(PassiveUpdateMode.CHANGES);
    session.addListener(new IListener()
    {
      public void notifyEvent(IEvent event)
      {
        if (event instanceof CDOSessionInvalidationEvent)
        {
          CDOSessionInvalidationEvent e = (CDOSessionInvalidationEvent)event;
          for (CDORevisionKey key : e.getChangedObjects())
          {
            CDORevisionDelta revisionDelta = (CDORevisionDelta)key;
            System.out.println("Feature deltas: " + revisionDelta.getFeatureDeltas());
          }
        }
      }
    });
</pre>
<br>
A session can now be configured to receive full new revisions instead of only keys.

<pre>
    session.options().setPassiveUpdateMode(PassiveUpdateMode.ADDITIONS);
    session.addListener(new IListener()
    {
      public void notifyEvent(IEvent event)
      {
        if (event instanceof CDOSessionInvalidationEvent)
        {
          CDOSessionInvalidationEvent e = (CDOSessionInvalidationEvent)event;
          for (CDOIDAndVersion key : e.getNewObjects())
          {
            CDORevision revision = (CDORevision)key;
            System.out.println("Container of new object: " + revision.data().getContainerID());
          }
        }
      }
    });
</pre>
<br>
PassiveUpdateMode.ADDITIONS includes PassiveUpdateMode.CHANGES.<br>
The default option is PassiveUpdateMode.INVALIDATIONS.<br>
Note that the CDOSessionInvalidationEvent interface extends CDOCommitInfo.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=294528 294528].

=== Reattached objects now keep their original identity ===

Formerly objects that were first being detached from a transaction and then
being reattached to the same transaction were assigned a new identitiy.
That was annoying in use cases like drag and drop in a user interface.
Now the transaction remembers identities of detached objects and reassigns them if needed.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=294528 294528].

=== Push transactions have been introduced ===

The new push transactions support commits to local files. Later the changes can be read from this local file and pushed to a repository.

<pre>
    File file = new File("changes.bin");
    file.delete();

    // Open a new transaction and wrap it in a push transaction
    CDOPushTransaction transaction1 = new CDOPushTransaction(session.openTransaction(), file);

    // Commit to local file
    transaction1.commit();
    transaction1.close();

    // Open a new push transaction and load changes from local file
    CDOPushTransaction transaction2 = new CDOPushTransaction(session.openTransaction(), file);

    // Push changes to the repository
    transaction2.push();
    transaction2.close();
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=297967 297967].

=== Changes in a local transaction can be exported and imported ===

The method exportChanges() has been added to CDOTransaction.

<pre>
    OutputStream fos = new FileOutputStream("changes.bin");

    try
    {
      CDOSavepoint[] savepoints = transaction1.exportChanges(fos);
    }
    finally
    {
      IOUtil.close(fos);
    }
</pre>
<br>
The method importChanges() has been added to CDOTransaction.

<pre>
    InputStream fis = new FileInputStream("changes.bin");

    try
    {
      CDOSavepoint[] savepoints = transaction2.importChanges(fis, true);
    }
    finally
    {
      IOUtil.close(fis);
    }
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=298334 298334].

=== Successful commits can now be queried ===

The concept of ''successful commits'' has been made explicit and queriable through CDOCommitInfoManager.

<pre>
    CDOBranchManager branchManager = session.getBranchManager();
    CDOBranch mainBranch = branchManager.getMainBranch();

    CDOCommitInfoManager commitInfoManager = session.getCommitInfoManager();
    commitInfoManager.getCommitInfos(
        mainBranch,
        CDOBranchPoint.UNSPECIFIED_DATE,
        CDOBranchPoint.UNSPECIFIED_DATE,
        new CDOCommitInfoHandler()
        {
          public void handleCommitInfo(CDOCommitInfo commitInfo)
          {
            System.out.println("Commit comment: " + commitInfo.getComment());
          }
        });
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=256649 256649].

=== Better type support for queries ===

Several small enhancements have been applied to query and query result transport:

* Query parameters can carry enum values instead of integer numbers, see bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=282610 282610].
* Query results carry enum values instead of integer numbers, see bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=283117 283117].
* Query results can carry arrays of primitive types, see bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=282612 282612].

=== New call-back hook after object state changes ===

Provides a way of being notified about state changes of objects.

<pre>
    CDOView view = session.openView();
    view.addObjectHandler(new CDOObjectHandler()
    {
      public void objectStateChanged(CDOView view, CDOObject object, CDOState oldState, CDOState newState)
      {
        System.out.println("Object transitioned to " + newState);
      }
    });
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=292733 292733].

=== New call-back hook after branch point changes of a view ===

Provides a way of being notified about target changes of views.

<pre>
    CDOView view = session.openView();
    view.addListener(new IListener()
    {
      public void notifyEvent(IEvent event)
      {
        if (event instanceof CDOViewTargetChangedEvent)
        {
          CDOViewTargetChangedEvent e = (CDOViewTargetChangedEvent)event;
          System.out.println("A new view target has been set: " + e.getBranchPoint());
        }
      }
    });

    view.setBranch(view.getSession().getBranchManager().getMainBranch());
    view.setTimeStamp(CDOBranchPoint.UNSPECIFIED_DATE);
    view.setBranchPoint(anotherView);
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=289880 289880].

=== New call-back hook after adapter notification ===

Since adapter notification can cause side effects and the order of the
notifications is unpredictable a new event is emitted by views to indicate the end of a
notification series.

<pre>
    CDOView view = session.openView();
    view.addListener(new IListener()
    {
      public void notifyEvent(IEvent event)
      {
        if (event instanceof CDOViewAdaptersNotifiedEvent)
        {
          CDOViewAdaptersNotifiedEvent e = (CDOViewAdaptersNotifiedEvent)event;
          System.out.println("All adapters have been notified about commit " + e.getTimeStamp());
        }
      }
    });
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=289880 289880].

=== New call-back hook after successful commits ===

Provides a way to handle transactions after they have been committed to the backend store.

<pre>
    IRepository repository = CDOServerUtil.createRepository("myrepo", store, props);

    repository.addHandler(new IRepository.WriteAccessHandler()
    {
      public void handleTransactionBeforeCommitting(ITransaction transaction,
          IStoreAccessor.CommitContext commitContext, OMMonitor monitor) throws RuntimeException
      {
        System.out.println("About to commit " + transaction);
      }

      public void handleTransactionAfterCommitted(ITransaction transaction,
          IStoreAccessor.CommitContext commitContext, OMMonitor monitor)
      {
        System.out.println("Committed " + transaction);
      }
    });
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=304959 304959].

=== New call-back hook after repository state change ===

The repository state can change in an offline clone or in the backup repository of a fail-over scenario. The possible states are: INITIAL, OFFLINE, SYNCING and ONLINE.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=256936 256936].

=== New call-back hook after repository type change ===

The repository type can change in the master and the backup repository of a fail-over scenario. The possible states are: INITIAL, OFFLINE, SYNCING and ONLINE.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=203167 203167].

=== New API to distinguish local commits from remote commits ===

An isRemote() method has been added to CDOSessionInvalidationEvent.

<pre>
    session.addListener(new IListener()
    {
      public void notifyEvent(IEvent event)
      {
        if (event instanceof CDOSessionInvalidationEvent)
        {
          CDOSessionInvalidationEvent e = (CDOSessionInvalidationEvent)event;
          if (e.isRemote())
          {
            System.out.println("A remote session has committed a transaction: " + e.getUserID());
          }
        }
      }
    });
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=281566 281566].

== DB Store ==

=== New alternative mapping strategy for increased performance for particular list operations ===

The default mapping strategy for the audit-aware DBStore usually writes value lists (for many-valued
features) completely for each revision. The new mapping strategy type <code>horizontalAuditWithRanges</code>
uses a different approch: each list value is associated with a start and end revision indicating its
lifetime. For applications in which lists are mostly extended by adding elements to the end of the lists,
using this mapping strategy can lead to increased performance.

However, in cases where lists are rearranged, of if list entries in the middle of the list are removed regularly,
the default mapping should still be the better choice. Also, as of now, this strategy can not be used together
with the new branching feature.

To activate the alternative mapping strategy, use the following configuration setting in the server's configuration.xml:

<pre>
<store type="db">
  <mappingStrategy type="horizontalAuditWithRanges">
    <property name="qualifiedNames" value="false"/>
  </mappingStrategy>
  ...
</store>
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=296440 296440].

=== New model annotations are available to override mapping defaults ===

The following new annotations are now recognized by the DBStore:

<pre>
    public enum DBAnnotation
    {
      TABLE_MAPPING("tableMapping"),
      TABLE_NAME("tableName"),
      COLUMN_NAME("columnName"),
      COLUMN_TYPE("columnType"),
      COLUMN_LENGTH("columnLength");

      public final static String SOURCE_URI = "http://www.eclipse.org/CDO/DBStore";
    }
</pre>
<br>
In CDO a structural feature can have multiple annotation with the same source, like:

<pre>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="value"
                eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
      <eAnnotations source="http://www.eclipse.org/CDO/DBStore">
        <details key="columnName" value="HOLY"/>
      </eAnnotations>
      <eAnnotations source="http://www.eclipse.org/CDO/DBStore">
        <details key="columnType" value="CLOB"/>
      </eAnnotations>
    </eStructuralFeatures>
</pre>
<br>
See bugzillas [https://bugs.eclipse.org/bugs/show_bug.cgi?id=284701 284701], [https://bugs.eclipse.org/bugs/show_bug.cgi?id=282976 282976], [https://bugs.eclipse.org/bugs/show_bug.cgi?id=284680 284680].

=== External references are now supported ===

External references are created if an object managed by a transaction refers to an object
that is either not managed by a view or is managed by a view to a different repository.
Internally such references are represented by instances of CDOIDExternal and their
CDOID.isExternal() method always returns true. In the database they appear as negative long values,
indicating that the URI of the target object can be looked up in a separate table.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=249610 249610].

=== Feature maps are now supported ===

If your model contains feature maps you can now use the DBStore to persist them.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=254455 254455].

=== Unsettable Features are now supported properly ===

For EStructuralFeatures which have the <code>unsettable</code> property set to <code>true</code>,
an additional boolean column is created in the corresponding database table which indicates if
the feature is set. If an unset feature is stored, <code>false</code> is stored in the additional
column and the feature's default value is stored in the feature's value field.

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=284110 284110].

=== SQL is now supported as a query language ===

If you are aware of the structure and meaning of a DBStore created mapping schema you can now
use SQL to query the backend efficiently. Note that might need to consider version and/or branch information to prevent duplicate results from being returned.

<pre>
    CDOQuery query = view.createQuery("sql", "SELECT CDO_ID FROM CUSTOMER ORDER BY NAME");
    List<Customer> customers = query.getResult(Customer.class);
</pre>
<br>
See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=248933 248933].

== Hibernate Store ==

=== Documentation has been completed ===

The documentation of the Hibernate store has been completed and is available on the [http://wiki.eclipse.org/CDO_Hibernate_Store CDO wiki]. The documentation includes a [http://wiki.eclipse.org/CDO_Hibernate_Store_Quick_Start quick start] and [http://wiki.eclipse.org/CDO_Hibernate_Store_Start_Tutorial tutorial], an overview article on the [http://wiki.eclipse.org/CDO_Hibernate_Store_Architecture architecture], [http://wiki.eclipse.org/CDO_Hibernate_Store_Download_and_Install download and install], [http://wiki.eclipse.org/CDO_Hibernate_Store_Model_Relational_Mapping model relational mapping] specifics and details on the [http://wiki.eclipse.org/CDO_Hibernate_Store_HQL HQL] support.

=== Example projects added ===
To ease the learning curve and facilitate starting with the Hibernate store [http://wiki.eclipse.org/CDO_Hibernate_Store_Download_and_Install#Example_Projects 2 example development projects] have been created and are downloadable from cvs. The example projects show how to setup the server side and shows examples of querying and updating on the client using the CDO-Hibernate store.

=== HQL is supported as a query language ===

The Hibernate store now supports HQL on the client. Practically the complete HQL syntax is supported:
* paged queries
* parameterized queries
* queries with functions
* queries which return primitive valued objects (not only CDOObjects)

In addition asynchronous queries are supported.

For more information on the HQL support see [http://wiki.eclipse.org/CDO_Hibernate_Store_HQL this] wiki page.

=== Teneo extension mechanism support added ===

The CDO Hibernate store supports the Teneo extension mechanism. This allows you to replace core parts of Teneo's mapping logic with your own implementation. See [http://wiki.eclipse.org/CDO_Hibernate_Store_Model_Relational_Mapping#Teneo_Extensions here] for more information.

=== External references are supported ===

By using special the special @External annotation it is now possible to persist references to objects which are not persisted directly in the CDO Hibernate store. See [http://wiki.eclipse.org/Teneo/Hibernate/ModelRelational/Association_Mapping#Storing_external_references_.28to_non-persisted_objects.29.2C_customizing_persisting_references this Teneo documentation] for more information.

=== Feature Map support has been added ===

Feature maps are now supported out-of-the-box. See [http://wiki.eclipse.org/Teneo/Hibernate/XMLSchema/FeatureMap this Teneo documentation] for a general discussion on featuremap support, the CDO Hibernate store supports a similar mechanism.

== Objectivity Store ==

=== Databases from Objectivity Inc. are now supported ===

See bugzilla [https://bugs.eclipse.org/bugs/show_bug.cgi?id=277420 277420].
