cancel
Showing results for 
Search instead for 
Did you mean: 

Sybase configuration troubles

kirillka
Champ in-the-making
Champ in-the-making
Hi,

I have created a new Sybase db to work with my alfresco install (out of the box war 2.0.0 deployed into tomcat 5.5), modified the db properties to connect to this particular database and to use the Sybase dialect of Hibernate, and I get the following errors:

11:07:51,986 ERROR [org.alfresco.repo.domain.schema.SchemaBootstrap] Statement execution failed:
   SQL:  create table alf_access_control_entry ( id numeric(19,0) identity not null, acl_id numeric(19,0) not null, permission_id numeric(19,0) not null, authority_id text not null, allowed tinyint not null, primary key (id), unique (acl_id, permission_id, authority_id) )
   Error: Column 'authority_id' – Can't create index on a column of TEXT or IMAGE data type.

File: /var/tmp/kuula/kirilllnx.sc.9001/temp/Alfresco/AlfrescoSchemaCreate-org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect-29843.sql
   Line: 393
11:07:52,029 ERROR [org.springframework.web.context.ContextLoader] Context initialization failed

Here's the version of Sybase i am running:

Adaptive Server Enterprise/12.5.3/EBF 13400 ESD#6 ONE-OFF/P/Linux Intel/Enterprise Linux/ase1253/1947/32-bit/OPT/Mon Mar  6 20:22:17 2006

So it looks to me that the Hibernate dialect is generating the invalid T-SQL for my Sybase database. Please help!

Thanks, Kirill
9 REPLIES 9

kirillka
Champ in-the-making
Champ in-the-making
Oh, and one more observation.

Looks like Alfresco is trying to use CREATE TABLE inside a transactino on a tempdb database. This is explicitly discouraged by Sybase manual (http://manuals.sybase.com/onlinebooks/group-as/asg1250e/refman/@Generic__BookTextView/94304;pt=94304..., search for 'ddl on tran'). but Alfresco does it anyways.

Why???

Here's the error in alfresc .log:

com.sybase.jdbc2.jdbc.SybSQLException: The 'CREATE TABLE' command is not allowed within a multi-statement transaction in the 'tempdb' database.

Thanks, Kirill

andy
Champ on-the-rise
Champ on-the-rise
Hi

Which hibernate dialect are you using?
We have a modified version.

Why can you not have an index on a TEXT column??
This has worked for me.

Have you any more details on when this sql is called or the actual statement?

Cheers

Andy

derek
Star Contributor
Star Contributor
There are no problems installing to non-TempDB on Sybase.

Regards

kirillka
Champ in-the-making
Champ in-the-making
Hi,

Turns out that in order to resolve the issue with the Sybase error that prevented creation of an index on a TEXT or IMAGE field, I needed to extend the SybaseAnywhereDialect class and effectively use varchar instead of TEXT.

The other error (about failing to run CREATE TABLE in a transaction on tempdb database) was caused by Alfresco getting confused after a tomcat restart. Basically, every time I restart tomcat I get that error. I need to wipe out the Alfresco data directory and the Alfresco database, and start tomcat then. Then it works and Alfresco starts up successfully. Needless to say this is unsatisfactory.

Any suggestions on how to make Alfresco survicve the tomcat restart??

Thanks, Kirill

derek
Star Contributor
Star Contributor
Hi,

Firstly, we have an AlfrescoSybaseAnywhereDialect.  It's exactly the same as yours.

We have many instances of Alfresco running (and restarting) without the issue.  I quite enjoy using Sybase in my dev environment, but I've yet to see any issue such as yours.  The really odd thing about your situation is that the server will not be attempting to create any tables after the first start.  Post a copy of the log, including the exception.  Let's just make sure about where it is failing.

Regards

kirillka
Champ in-the-making
Champ in-the-making
Hi Derek,

When you say
we have an AlfrescoSybaseAnywhereDialect. It's exactly the same as yours
does it mean that you also substitute TEXT with VARCHAR? Just curious…

As to your other comment that Alfresco in your installation restarts just fine, i am sure this is how it should be, and I am probably doing something wrong…

Here's my alfresco.log after a restart (btw, my alfresco.log gets created in the tomcat home dir, how do i move it to {tomcat}/logs?)

Thanks, kirill

11:49:29,508 WARN  [org.springframework.remoting.rmi.RmiRegistryFactoryBean] Could not detect RMI registry - creating new one
11:49:35,292 WARN  [org.alfresco.util.OpenOfficeConnectionTester] A connection to OpenOffice could not be established.
11:49:38,530 ERROR [org.springframework.web.context.ContextLoader] Context initialization failed
org.alfresco.error.AlfrescoRuntimeException: Schema auto-update failed
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:659)
        at org.alfresco.util.AbstractLifecycleBean.onApplicationEvent(AbstractLifecycleBean.java:62)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:45)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:225)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:323)
        at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:134)
        at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:246)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3729)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4187)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:739)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:524)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:809)
        at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:698)
        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:472)
        at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1122)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:310)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1021)
        at org.apache.catalina.core.StandardHost.start(StandardHost.java:718)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1013)
        at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:442)
        at org.apache.catalina.core.StandardService.start(StandardService.java:450)
        at org.apache.catalina.core.StandardServer.start(StandardServer.java:709)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:551)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:294)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:432)
Caused by: org.hibernate.exception.GenericJDBCException: could not get table metadata: JBPM_ACTION
        at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:100)
        at org.hibernate.cfg.Configuration.generateSchemaUpdateScript(Configuration.java:838)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.updateSchema(SchemaBootstrap.java:376)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:643)
        … 32 more
Caused by: com.sybase.jdbc3.jdbc.SybSQLException: The 'CREATE TABLE' command is not allowed within a multi-statement transaction in the 'tempdb' database.

        at com.sybase.jdbc3.tds.Tds.a(Unknown Source)
        at com.sybase.jdbc3.tds.Tds.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.ResultGetter.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.queryLoop(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybCallableStatement.executeQuery(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.if(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.getColumns(Unknown Source)
        at org.hibernate.tool.hbm2ddl.TableMetadata.initColumns(TableMetadata.java:193)
        at org.hibernate.tool.hbm2ddl.TableMetadata.<init>(TableMetadata.java:33)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:85)
        … 35 more
11:49:38,535 ERROR [org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/alfresco]] Exception sending context initialized event to listener insta
nce of class org.springframework.web.context.ContextLoaderListener
org.alfresco.error.AlfrescoRuntimeException: Schema auto-update failed
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:659)
        at org.alfresco.util.AbstractLifecycleBean.onApplicationEvent(AbstractLifecycleBean.java:62)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:45)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:225)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:323)
        at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:134)
        at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:246)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3729)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4187)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:739)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:524)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:809)
        at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:698)
        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:472)
        at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1122)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:310)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1021)
        at org.apache.catalina.core.StandardHost.start(StandardHost.java:718)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1013)
        at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:442)
        at org.apache.catalina.core.StandardService.start(StandardService.java:450)
        at org.apache.catalina.core.StandardServer.start(StandardServer.java:709)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:551)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:294)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:432)
Caused by: org.hibernate.exception.GenericJDBCException: could not get table metadata: JBPM_ACTION
        at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:100)
        at org.hibernate.cfg.Configuration.generateSchemaUpdateScript(Configuration.java:838)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.updateSchema(SchemaBootstrap.java:376)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:643)
        … 32 more
Caused by: com.sybase.jdbc3.jdbc.SybSQLException: The 'CREATE TABLE' command is not allowed within a multi-statement transaction in the 'tempdb' database.

        at com.sybase.jdbc3.tds.Tds.a(Unknown Source)
        at com.sybase.jdbc3.tds.Tds.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.ResultGetter.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.queryLoop(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybCallableStatement.executeQuery(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.if(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.getColumns(Unknown Source)
        at org.hibernate.tool.hbm2ddl.TableMetadata.initColumns(TableMetadata.java:193)
        at org.hibernate.tool.hbm2ddl.TableMetadata.<init>(TableMetadata.java:33)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:85)
        … 35 more
11:49:38,540 ERROR [org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/alfresco]] Exception sending context initialized event to listener insta
nce of class org.alfresco.web.app.ContextListener
org.alfresco.error.AlfrescoRuntimeException: Schema auto-update failed
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:659)
        at org.alfresco.util.AbstractLifecycleBean.onApplicationEvent(AbstractLifecycleBean.java:62)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:45)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:225)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:323)
        at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:134)
        at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:246)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3729)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4187)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:739)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:524)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:809)
        at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:698)
        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:472)
        at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1122)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:310)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1021)
        at org.apache.catalina.core.StandardHost.start(StandardHost.java:718)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1013)
        at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:442)
        at org.apache.catalina.core.StandardService.start(StandardService.java:450)
        at org.apache.catalina.core.StandardServer.start(StandardServer.java:709)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:551)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:294)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:432)
Caused by: org.hibernate.exception.GenericJDBCException: could not get table metadata: JBPM_ACTION
        at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:100)
        at org.hibernate.cfg.Configuration.generateSchemaUpdateScript(Configuration.java:838)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.updateSchema(SchemaBootstrap.java:376)
        at org.alfresco.repo.domain.schema.SchemaBootstrap.onBootstrap(SchemaBootstrap.java:643)
        … 32 more
Caused by: com.sybase.jdbc3.jdbc.SybSQLException: The 'CREATE TABLE' command is not allowed within a multi-statement transaction in the 'tempdb' database.

        at com.sybase.jdbc3.tds.Tds.a(Unknown Source)
        at com.sybase.jdbc3.tds.Tds.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.ResultGetter.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.nextResult(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybStatement.queryLoop(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybCallableStatement.executeQuery(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.if(Unknown Source)
        at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.getColumns(Unknown Source)
        at org.hibernate.tool.hbm2ddl.TableMetadata.initColumns(TableMetadata.java:193)
        at org.hibernate.tool.hbm2ddl.TableMetadata.<init>(TableMetadata.java:33)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:85)
        … 35 more

derek
Star Contributor
Star Contributor
does it mean that you also substitute TEXT with VARCHAR?
Yes.

It would appear as if the SybDatabaseMetaData is attempting to create a table when generating the results for getColumns.  Since Hibernate won't be issuing a getColumns call on first startup, it explains why it only happens subsequently.

The first thing you can do, just to get the server running, is to switch off the schema update feature after first startup.  Add the following property to the file containing your database configuration settings:
db.schema.update=false
This will turn off the Hibernate part of the schema checks, although there will still be a table query to find the alf_applied_patch table.

The next thing is to figure out what is missing that the metadata query has to create tables.  I'd start with articles such as http://manuals.sybase.com/onlinebooks/group-jc/jcg0600e/prjdbc/@Generic__BookTextView/3878;pt=3956.  If you can look at the JConnect code, it might reveal what is going on.  Perhaps the absence of some stored procedures leads to this behaviour.

Now check your Sybase server vs JConnect versions.  Here are the details from my jconn2.jar.
Name: com/sybase/jdbc2/jdbc
Specification-Title: "jConnect for JDBC 2.0"
Implementation-Version: "Build (25827)"
Specification-Vendor: "Sybase, Inc."
Specification-Version: "5.5"
Implementation-Title: "com.sybase.jdbc2.jdbc"
Implementation-Vendor: "Sybase, Inc."
and I've got Sybase AS 9.0.2.

Regards

kirillka
Champ in-the-making
Champ in-the-making
Derek,

Thanks for help. By setting db.schema.update to false (after the first run) I was able to restart tomcat successfully. Great! It does do the patch query, but it doesn't cause an error:
14:46:04,575 INFO  [org.alfresco.repo.admin.patch.PatchExecuter] Checking for patches to apply …
14:46:07,753 INFO  [org.alfresco.repo.module.ModuleServiceImpl] Found 0 module(s).
Now, I think I vaguely remember that I used to have similar metadata-related problems on different projects with older versions of JConnect and our Sybase db. Basically, i don't think we have the metadata stored procs installed there at all - hence the error… I just haven't done any jconnect stuff for a long time and now it's all coming back.  Smiley Very Happy

The version I am using is 6.0.2:
Name: com/sybase/jdbcx/
Specification-Title: "jConnect for JDBC 3.0"
Implementation-Title: "com.sybase.jdbcx"
Specification-Version: "6.0"
Specification-Vendor: "Sybase, Inc."
Implementation-Version: "Build (25904)"
Implementation-Vendor: "Sybase, Inc."
My sybase version is 12.5.3:
Adaptive Server Enterprise/12.5.3/EBF 13400 ESD#6 ONE-OFF/P/Linux Intel/Enterprise Linux/ase1253/1947/32-bit/OPT/Mon Mar  6 20:22:17 2006
I don't think it matters though, both JConnect 5.5 and 6.0 require stored procs for metadata… i will see if I can have them installed…

Thanks again for your suggestions!

Kirill

derek
Star Contributor
Star Contributor
I'm glad you'll get to play with the product at least.  Perhaps some other Sybase developers might know why this issue arises.
Good luck.
Getting started

Tags


Find what you came for

We want to make your experience in Hyland Connect as valuable as possible, so we put together some helpful links.